diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts
index 97b469d8a5dc..836007d29cd2 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-item/entity-item-ref/entity-item-ref.element.ts
@@ -1,15 +1,16 @@
import type { ManifestEntityItemRef } from './entity-item-ref.extension.js';
-import { customElement, property, type PropertyValueMap, state, css, html } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api';
-import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
+import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
import { UmbRoutePathAddendumContext } from '@umbraco-cms/backoffice/router';
-import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const';
import { UUIBlinkAnimationValue } from '@umbraco-cms/backoffice/external/uui';
+import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
+import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import './default-item-ref.element.js';
-import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
@customElement('umb-entity-item-ref')
export class UmbEntityItemRefElement extends UmbLitElement {
@@ -20,9 +21,6 @@ export class UmbEntityItemRefElement extends UmbLitElement {
private _component?: any; // TODO: Add type
@property({ type: Object, attribute: false })
- public get item(): UmbEntityModel | undefined {
- return this.#item;
- }
public set item(value: UmbEntityModel | undefined) {
const oldValue = this.#item;
this.#item = value;
@@ -41,6 +39,9 @@ export class UmbEntityItemRefElement extends UmbLitElement {
// If the component is already created, but the entity type is different, we need to destroy the component.
this.#createController(value.entityType);
}
+ public get item(): UmbEntityModel | undefined {
+ return this.#item;
+ }
#readonly = false;
@property({ type: Boolean, reflect: true })
@@ -124,20 +125,23 @@ export class UmbEntityItemRefElement extends UmbLitElement {
error?: boolean;
@property({ type: String, attribute: 'error-message', reflect: false })
- errorMessage?: string;
+ errorMessage?: string | null;
+
+ @property({ type: String, attribute: 'error-detail', reflect: false })
+ errorDetail?: string | null;
#pathAddendum = new UmbRoutePathAddendumContext(this);
#onSelected(event: UmbSelectedEvent) {
event.stopPropagation();
- const unique = this.#item?.unique;
+ const unique = this.item?.unique;
if (!unique) throw new Error('No unique id found for item');
this.dispatchEvent(new UmbSelectedEvent(unique));
}
#onDeselected(event: UmbDeselectedEvent) {
event.stopPropagation();
- const unique = this.#item?.unique;
+ const unique = this.item?.unique;
if (!unique) throw new Error('No unique id found for item');
this.dispatchEvent(new UmbDeselectedEvent(unique));
}
@@ -163,7 +167,7 @@ export class UmbEntityItemRefElement extends UmbLitElement {
// TODO: I would say this code can use feature of the UmbExtensionsElementInitializer, to set properties and get a fallback element. [NL]
// assign the properties to the component
- component.item = this.#item;
+ component.item = this.item;
component.readonly = this.readonly;
component.standalone = this.standalone;
component.selectOnly = this.selectOnly;
@@ -192,20 +196,25 @@ export class UmbEntityItemRefElement extends UmbLitElement {
if (this._component) {
return html`${this._component}`;
}
+
// Error:
if (this.error) {
- return html`
-
-
- `;
+ return html`
+
+
+
+
+ `;
}
+
// Loading:
return html``;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts
index 2829e753db6b..9c90d216c05f 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker-input/picker-input.context.ts
@@ -31,8 +31,8 @@ export class UmbPickerInputContext<
public readonly interactionMemory = new UmbInteractionMemoryManager(this);
/**
- * Define a minimum amount of selected items in this input, for this input to be valid.
- * @returns {number} The minimum number of items required.
+ * Define a maximum amount of selected items in this input, for this input to be valid.
+ * @returns {number} The maximum number of items required.
*/
public get max() {
return this._max;
@@ -43,7 +43,7 @@ export class UmbPickerInputContext<
private _max = Infinity;
/**
- * Define a maximum amount of selected items in this input, for this input to be valid.
+ * Define a minimum amount of selected items in this input, for this input to be valid.
* @returns {number} The minimum number of items required.
*/
public get min() {
@@ -111,21 +111,32 @@ export class UmbPickerInputContext<
this.getHostElement().dispatchEvent(new UmbChangeEvent());
}
+ /**
+ * Get the display name for an item to show in the remove confirmation dialog.
+ * Subclasses can override this to provide custom formatting for missing items.
+ * @param item - The item to get the display name for, or undefined if not found
+ * @param unique - The unique identifier of the item
+ * @returns The display name to show in the dialog
+ */
+ protected getItemDisplayName(item: PickedItemType | undefined, unique: string): string {
+ return item?.name ?? unique;
+ }
+
async requestRemoveItem(unique: string) {
const item = this.#itemManager.getItems().find((item) => item.unique === unique);
+ const name = this.getItemDisplayName(item, unique);
- const name = item?.name ?? '#general_notFound';
await umbConfirmModal(this, {
color: 'danger',
- headline: `#actions_remove ${name}?`,
+ headline: `#actions_remove?`,
content: `#defaultdialogs_confirmremove ${name}?`,
confirmLabel: '#actions_remove',
});
- this.#removeItem(unique);
+ this._removeItem(unique);
}
- #removeItem(unique: string) {
+ protected _removeItem(unique: string) {
const newSelection = this.getSelection().filter((value) => value !== unique);
this.setSelection(newSelection);
this.getHostElement().dispatchEvent(new UmbChangeEvent());
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts
index 01f74f88bce0..cbff030a4c85 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts
@@ -2,13 +2,16 @@ import type { UmbDocumentTypeItemModel, UmbDocumentTypeTreeItemModel } from '../
import { UMB_DOCUMENT_TYPE_WORKSPACE_MODAL } from '../../constants.js';
import { UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../../paths.js';
import { UmbDocumentTypePickerInputContext } from './input-document-type.context.js';
-import { css, html, customElement, property, state, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
+import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
-import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
+
+import '@umbraco-cms/backoffice/entity-item';
@customElement('umb-input-document-type')
export class UmbInputDocumentTypeElement extends UmbFormControlMixin(
@@ -112,6 +115,9 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin;
+ @state()
+ private _statuses?: Array;
+
@state()
private _editPath = '';
@@ -143,6 +149,7 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
protected override getFormElement() {
@@ -151,8 +158,8 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin !x.isFolder && x.isElement === false;
}
@@ -184,8 +191,8 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this.#renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => {
+ const unique = status.unique;
+ const item = this._items?.find((x) => x.unique === unique);
+ const isError = status.state.type === 'error';
+
+ // For error state, use umb-entity-item-ref
+ if (isError) {
+ return html`
+
+ ${when(
+ !this.readonly,
+ () => html`
+
+ this.#removeItem(unique)}>
+
+ `,
+ )}
+
+ `;
+ }
+
+ // For successful items, use the document type specific component
+ if (!item) return nothing;
+ const href = this._editPath + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique });
+ return html`
+
+ ${this.#renderIcon(item)}
+
+ ${when(
+ !this.readonly,
+ () => html`
+ this.#removeItem(unique)}>
+ `,
+ )}
+
+
+ `;
+ },
)}
`;
}
- #renderItem(item: UmbDocumentTypeItemModel) {
- if (!item.unique) return;
- const href = this._editPath + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique });
- return html`
-
- ${this.#renderIcon(item)}
-
- ${when(
- !this.readonly,
- () => html`
- this.#removeItem(item)}>
- `,
- )}
-
-
- `;
- }
-
#renderIcon(item: UmbDocumentTypeItemModel) {
if (!item.icon) return;
return html``;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts
index 516251a4b5b9..302d9594c7ee 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts
@@ -239,24 +239,28 @@ export class UmbInputDocumentElement extends UmbFormControlMixin {
const unique = status.unique;
const item = this._items?.find((x) => x.unique === unique);
- return html`
- ${when(
- !this.readonly,
- () => html`
-
- this.#onRemove(unique)}>
-
- `,
- )}
- `;
+ const isError = status.state.type === 'error';
+ return html`
+
+ ${when(
+ !this.readonly,
+ () => html`
+
+ this.#onRemove(unique)}>
+
+ `,
+ )}
+
+ `;
},
)}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.element.ts b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.element.ts
index 826bbe0b83d6..9a462669437c 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/language/components/input-language/input-language.element.ts
@@ -6,6 +6,7 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
@customElement('umb-input-language')
export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement, '') {
@@ -17,7 +18,7 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
return modelEntry;
},
identifier: 'Umb.SorterIdentifier.InputLanguage',
- itemSelector: 'uui-ref-node',
+ itemSelector: 'umb-entity-item-ref',
containerSelector: 'uui-ref-list',
onChange: ({ model }) => {
this.selection = model;
@@ -115,6 +116,9 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
@state()
private _items: Array = [];
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbLanguagePickerInputContext(this);
constructor() {
@@ -134,6 +138,7 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
protected override getFormElement() {
@@ -147,8 +152,8 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
});
}
- #onRemove(item: UmbLanguageItemModel) {
- this.#pickerContext.requestRemoveItem(item.unique);
+ #onRemove(unique: string) {
+ this.#pickerContext.requestRemoveItem(unique);
}
override render() {
@@ -167,29 +172,38 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
}
#renderItems() {
- if (!this._items) return;
+ if (!this._statuses) return;
return html`
${repeat(
- this._items,
- (item) => item.unique,
- (item) =>
- html`
- ${when(
- !this.readonly,
- () => html`
-
- this.#onRemove(item)}>
-
- `,
- )}
- `,
+ this._statuses,
+ (status) => status.unique,
+ (status) => {
+ const unique = status.unique;
+ const item = this._items?.find((x) => x.unique === unique);
+ const isError = status.state.type === 'error';
+ return html`
+
+ ${when(
+ !this.readonly,
+ () => html`
+
+ this.#onRemove(unique)}>
+
+ `,
+ )}
+
+ `;
+ },
)}
`;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts
index cd659f441a16..d9e7949db0f0 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts
@@ -8,6 +8,9 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
+
+import '@umbraco-cms/backoffice/entity-item';
@customElement('umb-input-media-type')
export class UmbInputMediaTypeElement extends UmbFormControlMixin(
@@ -95,6 +98,9 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin;
+ @state()
+ private _statuses?: Array;
+
@state()
private _editPath = '';
@@ -126,6 +132,7 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
protected override getFormElement() {
@@ -138,8 +145,8 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this.#renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => {
+ const unique = status.unique;
+ const item = this._items?.find((x) => x.unique === unique);
+ const isError = status.state.type === 'error';
+
+ // For error state, use umb-entity-item-ref
+ if (isError) {
+ return html`
+
+
+ this.#removeItem(unique)}>
+
+
+ `;
+ }
+
+ // For successful items, use the media type specific component
+ if (!item) return nothing;
+ const href = `${this._editPath}edit/${unique}`;
+ return html`
+
+ ${this.#renderIcon(item)}
+
+
+ this.#removeItem(unique)}>
+
+
+ `;
+ },
)}
`;
}
- #renderItem(item: UmbMediaTypeItemModel) {
- if (!item.unique) return;
- const href = `${this._editPath}edit/${item.unique}`;
- return html`
-
- ${this.#renderIcon(item)}
-
-
- this.#removeItem(item)} label=${this.localize.term('general_remove')}>
-
-
- `;
- }
-
#renderIcon(item: UmbMediaTypeItemModel) {
if (!item.icon) return;
return html``;
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.element.ts
index bec7d38b13a7..40a8c9e3d5da 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/components/input-member-group/input-member-group.element.ts
@@ -1,13 +1,16 @@
import type { UmbMemberGroupItemModel } from '../../types.js';
import { UmbMemberGroupPickerInputContext } from './input-member-group.context.js';
-import { css, html, customElement, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
+import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
-import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
-import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
+
+import '@umbraco-cms/backoffice/entity-item';
@customElement('umb-input-member-group')
export class UmbInputMemberGroupElement extends UmbFormControlMixin(
@@ -124,6 +127,9 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin;
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbMemberGroupPickerInputContext(this);
constructor() {
@@ -152,6 +158,7 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observeItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
protected override getFormElement() {
@@ -164,8 +171,8 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this.#renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => {
+ const unique = status.unique;
+ const item = this._items?.find((x) => x.unique === unique);
+ const isError = status.state.type === 'error';
+
+ // For error state, use umb-entity-item-ref
+ if (isError) {
+ return html`
+
+ ${when(
+ !this.readonly,
+ () => html`
+
+ this.#removeItem(unique)}>
+
+ `,
+ )}
+
+ `;
+ }
+
+ // For successful items, use uui-ref-node
+ if (!item) return nothing;
+ return html`
+
+
+ ${when(
+ !this.readonly,
+ () =>
+ html` this.#removeItem(unique)}
+ label=${this.localize.term('general_remove')}>`,
+ )}
+
+
+
+ `;
+ },
)}
`;
@@ -199,27 +255,6 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin
- ${this.#renderRemoveButton(item)}
-
-
- `;
- }
-
- #renderRemoveButton(item: UmbMemberGroupItemModel) {
- if (this.readonly) return nothing;
- return html` this.#removeItem(item)}
- label=${this.localize.term('general_remove')}>`;
- }
-
static override styles = [
css`
#btn-add {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.element.ts
index dd887b2d66ee..6386a906c1de 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/components/input-member-type/input-member-type.element.ts
@@ -1,9 +1,12 @@
import { UmbMemberTypePickerInputContext } from './input-member-type.context.js';
-import { css, html, customElement, property, state, repeat, when } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
+import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models';
-import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
+
+import '@umbraco-cms/backoffice/entity-item';
@customElement('umb-input-member-type')
export class UmbInputMemberTypeElement extends UmbFormControlMixin(
@@ -73,6 +76,9 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin;
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbMemberTypePickerInputContext(this);
constructor() {
@@ -92,6 +98,7 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin (this.value = selection.join(',')));
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
protected override getFormElement() {
@@ -109,13 +116,13 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this.#renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => this.#renderItem(status),
)}
`;
@@ -134,14 +141,37 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin x.unique === unique);
+ const isError = status.state.type === 'error';
+
+ // For error state, use umb-entity-item-ref
+ if (isError) {
+ return html`
+
+
+ this.#pickerContext.requestRemoveItem(unique)}>
+
+
+ `;
+ }
+
+ // For successful items, use the member type specific component
+ if (!item?.unique) return nothing;
return html`
${when(item.icon, () => html``)}
this.#pickerContext.requestRemoveItem(item.unique!)}
+ @click=${() => this.#pickerContext.requestRemoveItem(unique)}
label="Remove Member Type ${item.name}"
>${this.localize.term('general_remove')}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts
index d5b60d9e0488..8ddf6e606824 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts
@@ -1,12 +1,13 @@
import type { UmbMemberItemModel } from '../../item/types.js';
import { UmbMemberPickerInputContext } from './input-member.context.js';
-import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
+import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/member-type';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
@customElement('umb-input-member')
export class UmbInputMemberElement extends UmbFormControlMixin(
@@ -121,6 +122,9 @@ export class UmbInputMemberElement extends UmbFormControlMixin;
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbMemberPickerInputContext(this);
constructor() {
@@ -140,6 +144,7 @@ export class UmbInputMemberElement extends UmbFormControlMixin (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observeItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
#openPicker() {
@@ -156,8 +161,8 @@ export class UmbInputMemberElement extends UmbFormControlMixin
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this.#renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => {
+ const unique = status.unique;
+ const item = this._items?.find((x) => x.unique === unique);
+ const isError = status.state.type === 'error';
+ return html`
+
+ ${when(
+ !this.readonly,
+ () => html`
+
+ this.#onRemove(unique)}>
+
+ `,
+ )}
+
+ `;
+ },
)}
`;
}
- #renderItem(item: UmbMemberItemModel) {
- if (!item.unique) return nothing;
- return html`
-
- ${this.#renderRemoveButton(item)}
-
- `;
- }
-
#renderAddButton() {
if (this.selection.length >= this.max) return nothing;
if (this.readonly && this.selection.length > 0) {
@@ -202,13 +223,6 @@ export class UmbInputMemberElement extends UmbFormControlMixin this.#onRemove(item)} label=${this.localize.term('general_remove')}>
- `;
- }
-
static override styles = [
css`
#btn-add {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts
index 7a6bc3826a84..75bbb7e31cfc 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.context.ts
@@ -1,8 +1,9 @@
import { UMB_STATIC_FILE_PICKER_MODAL } from '../../modals/index.js';
-import type { UmbStaticFilePickerModalData, UmbStaticFilePickerModalValue } from '../../modals/index.js';
import { UMB_STATIC_FILE_ITEM_REPOSITORY_ALIAS } from '../../constants.js';
+import type { UmbStaticFilePickerModalData, UmbStaticFilePickerModalValue } from '../../modals/index.js';
import type { UmbStaticFileItemModel } from '../../types.js';
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
+import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbStaticFilePickerInputContext extends UmbPickerInputContext<
@@ -11,7 +12,14 @@ export class UmbStaticFilePickerInputContext extends UmbPickerInputContext<
UmbStaticFilePickerModalData,
UmbStaticFilePickerModalValue
> {
+ #serializer = new UmbServerFilePathUniqueSerializer();
+
constructor(host: UmbControllerHost) {
super(host, UMB_STATIC_FILE_ITEM_REPOSITORY_ALIAS, UMB_STATIC_FILE_PICKER_MODAL);
}
+
+ protected override getItemDisplayName(item: UmbStaticFileItemModel | undefined, unique: string): string {
+ // If item doesn't exist, use the file path as the name
+ return item?.name ?? this.#serializer.toServerPath(unique) ?? unique;
+ }
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.element.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.element.ts
index 9e42a0b6f7b1..e533364de471 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/components/input-static-file/input-static-file.element.ts
@@ -2,9 +2,12 @@ import type { UmbStaticFileItemModel } from '../../repository/item/types.js';
import { UmbStaticFilePickerInputContext } from './input-static-file.context.js';
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
+import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system';
-import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
+
+import '@umbraco-cms/backoffice/entity-item';
@customElement('umb-input-static-file')
export class UmbInputStaticFileElement extends UmbFormControlMixin(
@@ -79,6 +82,9 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin;
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbStaticFilePickerInputContext(this);
constructor() {
@@ -98,6 +104,7 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin (this.value = selection.join(',')));
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses));
}
protected override getFormElement() {
@@ -105,13 +112,13 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this._renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => this.#renderItem(status),
)}
${this.#renderAddButton()}
@@ -137,17 +144,25 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin x.unique === unique);
+ const isError = status.state.type === 'error';
+
return html`
-
-
+
this.#pickerContext.requestRemoveItem(item.unique)}>
+ @click=${() => this.#pickerContext.requestRemoveItem(unique)}>
-
+
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/manifests.ts
index cd9c2b9c6406..261047fb1598 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/manifests.ts
@@ -4,7 +4,7 @@ export const manifest: ManifestPropertyEditorUi = {
type: 'propertyEditorUi',
alias: 'Umb.PropertyEditorUi.StaticFilePicker',
name: 'Static File Picker Property Editor UI',
- js: () => import('./property-editor-ui-static-file-picker.element.js'),
+ element: () => import('./property-editor-ui-static-file-picker.element.js'),
meta: {
label: 'Static File Picker',
icon: 'icon-document',
diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts
index 81cfbda18ddb..d5098b7700bf 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/property-editors/static-file-picker/property-editor-ui-static-file-picker.element.ts
@@ -73,6 +73,8 @@ export class UmbPropertyEditorUIStaticFilePickerElement extends UmbLitElement im
}
}
+export { UmbPropertyEditorUIStaticFilePickerElement as element };
+
export default UmbPropertyEditorUIStaticFilePickerElement;
declare global {
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts
index f509aee05805..d86884dc9007 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts
@@ -1,12 +1,24 @@
import { UMB_USER_GROUP_ENTITY_TYPE } from '../../entity.js';
import type { UmbUserGroupItemModel } from '../../repository/index.js';
import { UmbUserGroupPickerInputContext } from './user-group-input.context.js';
-import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
-import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
+import {
+ css,
+ customElement,
+ html,
+ ifDefined,
+ nothing,
+ property,
+ repeat,
+ state,
+} from '@umbraco-cms/backoffice/external/lit';
+import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
-import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
+import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
+import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
+
+import '@umbraco-cms/backoffice/entity-item';
@customElement('umb-user-group-input')
export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement, '') {
@@ -75,6 +87,9 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
@state()
private _items?: Array;
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbUserGroupPickerInputContext(this);
@state()
@@ -97,6 +112,7 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
.addAdditionalPath(UMB_USER_GROUP_ENTITY_TYPE)
@@ -114,7 +130,15 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
override render() {
return html`
- ${this._items?.map((item) => this._renderItem(item))}
+
+ ${this._statuses
+ ? repeat(
+ this._statuses,
+ (status) => status.unique,
+ (status) => this.#renderItem(status),
+ )
+ : nothing}
+
x.unique === unique);
+ const isError = status.state.type === 'error';
+
+ // For error state, use umb-entity-item-ref
+ if (isError) {
+ return html`
+
+
+ this.#pickerContext.requestRemoveItem(unique)}>
+
+
+ `;
+ }
+
+ // For successful items, use umb-user-group-ref
+ if (!item?.unique) return nothing;
+ const href = `${this._editUserGroupPath}edit/${unique}`;
return html`
${item.icon ? html`` : nothing}
this.#pickerContext.requestRemoveItem(item.unique)}
+ @click=${() => this.#pickerContext.requestRemoveItem(unique)}
label=${this.localize.term('general_remove')}>
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.element.ts
index f5621924100a..a1de877671c4 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-input/user-input.element.ts
@@ -6,6 +6,7 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
+import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
// TODO: Shall we rename to 'umb-input-user'? [LK]
@customElement('umb-user-input')
@@ -92,6 +93,9 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
@state()
private _items?: Array;
+ @state()
+ private _statuses?: Array;
+
#pickerContext = new UmbUserPickerInputContext(this);
constructor() {
@@ -111,6 +115,7 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
+ this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
}
protected override getFormElement() {
@@ -121,8 +126,8 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
this.#pickerContext.openPicker({});
}
- #removeItem(item: UmbUserItemModel) {
- this.#pickerContext.requestRemoveItem(item.unique);
+ #removeItem(unique: string) {
+ this.#pickerContext.requestRemoveItem(unique);
}
override render() {
@@ -141,24 +146,34 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
}
#renderItems() {
- if (!this._items) return nothing;
+ if (!this._statuses) return nothing;
return html`
${repeat(
- this._items,
- (item) => item.unique,
- (item) => this.#renderItem(item),
+ this._statuses,
+ (status) => status.unique,
+ (status) => this.#renderItem(status),
)}
`;
}
- #renderItem(item: UmbUserItemModel) {
- if (!item.unique) return nothing;
+ #renderItem(status: UmbRepositoryItemsStatus) {
+ const unique = status.unique;
+ const item = this._items?.find((x) => x.unique === unique);
+ const isError = status.state.type === 'error';
return html`
-
+
- this.#removeItem(item)}>
+ this.#removeItem(unique)}>
`;