Backoffice Item Pickers: Show error for missing items in 10 picker types (closes #19329, #20270, #20367) (#20762)

* Add errorDetail property to umb-entity-item-ref

Add optional errorDetail property to display additional context
(such as file paths or IDs) in error states. This enhances the
error display to show both the error message and relevant details.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Make _removeItem protected in UmbPickerInputContext

Change #removeItem from private to protected to allow subclasses
to reuse the removal logic while customizing the confirmation dialog.
This enables better extensibility for specialized picker contexts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix static file picker to show error state for missing files

Update umb-input-static-file to observe statuses and render based
on item state (loading, error, success). When a static file is
missing (API returns empty array), displays error state with alert
icon and file path detail using umb-entity-item-ref.

Also adds standalone property support for proper single-item styling.

Fixes #19329

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Show file path in static file remove confirmation dialog

Override requestRemoveItem in UmbStaticFilePickerInputContext to
display the file path instead of "Not found" in the confirmation
dialog when removing missing static files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Show GUID in document picker error state

Display the document GUID as errorDetail when a document is
not found (deleted/gone). This provides useful context for
editors to identify which document was referenced.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Show GUID in document picker remove confirmation dialog

Display the document GUID instead of "Not found" in the remove
confirmation dialog when the document no longer exists. This
provides useful context for editors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: apply the temp model which the context uses

* Refactor: Move requestRemoveItem logic to base UmbPickerInputContext

Eliminated duplicate code across three picker contexts by:
- Adding protected getItemDisplayName() method to base class
- Moving requestRemoveItem implementation to base class
- Removing duplicate implementations from document, member, and static file pickers
- Static file picker overrides getItemDisplayName() to show file path

Net reduction: 19 lines of code (69 removed, 50 added)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Document Type Picker: Show error state for missing items (fixes #20367)

Apply the same error state handling to the document type picker that was
implemented for static files, documents, and members. When a referenced
document type is missing or deleted:

- Show error state with the GUID as errorDetail
- Allow removal with proper confirmation dialog
- Use umb-entity-item-ref for error display
- Use uui-ref-node-document-type for successful items

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Additional pickers: Show error states for missing items in user, language, media-type, member-type, member-group, and user-group pickers

Apply the same error state handling pattern to six additional picker types:
- user-input: Users
- input-language: Languages
- input-media-type: Media types
- input-member-type: Member types
- input-member-group: Member groups
- user-group-input: User groups

All pickers now:
- Observe statuses from UmbRepositoryItemsManager
- Show error state with GUID when referenced item is missing/deleted
- Use umb-entity-item-ref for error display
- Use specialized components (uui-ref-node, umb-user-group-ref, etc.) for successful items
- Allow removal with proper confirmation dialog showing GUID

Maintains code reusability by using the base class requestRemoveItem method
with getItemDisplayName() for consistent error handling across all pickers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Lint: Remove unused 'when' imports from input-media-type and user-group-input

* Refactor: Add #renderItem helper method to all pickers for consistency

- Add #renderItem to user-input (extracted from inline repeat callback)
- Change _renderItem to #renderItem in user-group-input for consistency
- Change _renderItem to #renderItem in input-static-file for consistency

All 10 pickers now use consistent #renderItem helper method pattern,
improving code readability and maintainability as suggested by @nielslyngsoe

* `import` sorting

* Corrected (old) JSDoc typos

* Markup tidy-up

* exported `UmbPropertyEditorUIStaticFilePickerElement` as `element`

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
This commit is contained in:
Jacob Overgaard
2025-11-10 13:57:24 +01:00
committed by GitHub
parent afec900204
commit ab51aac5c6
15 changed files with 481 additions and 216 deletions

View File

@@ -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`<uui-ref-node
style="color: var(--uui-color-danger);"
.name=${this.localize.string(this.errorMessage ?? '#general_notFound')}
.readonly=${this.readonly}
.standalone=${this.standalone}
.selectOnly=${this.selectOnly}
.selected=${this.selected}
.disabled=${this.disabled}>
<uui-icon slot="icon" name="icon-alert" style="color: var(--uui-color-danger);"></uui-icon>
<slot name="actions"></slot>
</uui-ref-node>`;
return html`
<uui-ref-node
style="color: var(--uui-color-danger);"
.name=${this.localize.string(this.errorMessage ?? '#general_notFound')}
.detail=${this.errorDetail ?? ''}
.readonly=${this.readonly}
.standalone=${this.standalone}
.selectOnly=${this.selectOnly}
.selected=${this.selected}
.disabled=${this.disabled}>
<uui-icon slot="icon" name="icon-alert" style="color: var(--uui-color-danger);"></uui-icon>
<slot name="actions"></slot>
</uui-ref-node>
`;
}
// Loading:
return html`<uui-loader-bar style="margin-top:10px;"></uui-loader-bar>`;
}

View File

@@ -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());

View File

@@ -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<string | undefined, typeof UmbLitElement>(
@@ -112,6 +115,9 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
@state()
private _items?: Array<UmbDocumentTypeItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
@state()
private _editPath = '';
@@ -143,6 +149,7 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
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() {
@@ -184,8 +191,8 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
);
}
#removeItem(item: UmbDocumentTypeItemModel) {
this.#pickerContext.requestRemoveItem(item.unique);
#removeItem(unique: string) {
this.#pickerContext.requestRemoveItem(unique);
}
override render() {
@@ -204,38 +211,66 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
}
#renderItems() {
if (!this._items) return nothing;
if (!this._statuses) return nothing;
return html`
<uui-ref-list>
${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`
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${true}
.errorMessage=${status.state.error}
.errorDetail=${unique}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(unique)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>
`;
}
// 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`
<uui-ref-node-document-type id=${unique} name=${this.localize.string(item.name)} href=${href}>
${this.#renderIcon(item)}
<uui-action-bar slot="actions">
${when(
!this.readonly,
() => html`
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(unique)}></uui-button>
`,
)}
</uui-action-bar>
</uui-ref-node-document-type>
`;
},
)}
</uui-ref-list>
`;
}
#renderItem(item: UmbDocumentTypeItemModel) {
if (!item.unique) return;
const href = this._editPath + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique });
return html`
<uui-ref-node-document-type id=${item.unique} name=${this.localize.string(item.name)} href=${href}>
${this.#renderIcon(item)}
<uui-action-bar slot="actions">
${when(
!this.readonly,
() => html`
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(item)}></uui-button>
`,
)}
</uui-action-bar>
</uui-ref-node-document-type>
`;
}
#renderIcon(item: UmbDocumentTypeItemModel) {
if (!item.icon) return;
return html`<umb-icon slot="icon" name=${item.icon}></umb-icon>`;

View File

@@ -239,24 +239,28 @@ export class UmbInputDocumentElement extends UmbFormControlMixin<string | undefi
(status) => {
const unique = status.unique;
const item = this._items?.find((x) => x.unique === unique);
return html`<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${status.state.type === 'error'}
.errorMessage=${status.state.error}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#onRemove(unique)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>`;
const isError = status.state.type === 'error';
return html`
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${isError}
.errorMessage=${status.state.error}
.errorDetail=${isError ? unique : undefined}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#onRemove(unique)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>
`;
},
)}
</uui-ref-list>

View File

@@ -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<UmbLanguageItemModel> = [];
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#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`
<uui-ref-list>
${repeat(
this._items,
(item) => item.unique,
(item) =>
html`<umb-entity-item-ref
id=${item.unique}
.item=${item}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#onRemove(item)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>`,
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`
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${isError}
.errorMessage=${status.state.error}
.errorDetail=${isError ? unique : undefined}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#onRemove(unique)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>
`;
},
)}
</uui-ref-list>
`;

View File

@@ -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<string | undefined, typeof UmbLitElement>(
@@ -95,6 +98,9 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
@state()
private _items?: Array<UmbMediaTypeItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
@state()
private _editPath = '';
@@ -126,6 +132,7 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
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() {
@@ -138,8 +145,8 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
});
}
#removeItem(item: UmbMediaTypeItemModel) {
this.#pickerContext.requestRemoveItem(item.unique);
#removeItem(unique: string) {
this.#pickerContext.requestRemoveItem(unique);
}
override render() {
@@ -158,32 +165,56 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
}
#renderItems() {
if (!this._items) return nothing;
if (!this._statuses) return nothing;
return html`
<uui-ref-list>
${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`
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${true}
.errorMessage=${status.state.error}
.errorDetail=${unique}
?standalone=${this.max === 1}>
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(unique)}></uui-button>
</uui-action-bar>
</umb-entity-item-ref>
`;
}
// For successful items, use the media type specific component
if (!item) return nothing;
const href = `${this._editPath}edit/${unique}`;
return html`
<uui-ref-node-document-type name=${this.localize.string(item.name)} id=${unique}>
${this.#renderIcon(item)}
<uui-action-bar slot="actions">
<uui-button href=${href} label=${this.localize.term('general_open')}></uui-button>
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(unique)}></uui-button>
</uui-action-bar>
</uui-ref-node-document-type>
`;
},
)}
</uui-ref-list>
`;
}
#renderItem(item: UmbMediaTypeItemModel) {
if (!item.unique) return;
const href = `${this._editPath}edit/${item.unique}`;
return html`
<uui-ref-node-document-type name=${this.localize.string(item.name)} id=${item.unique}>
${this.#renderIcon(item)}
<uui-action-bar slot="actions">
<uui-button href=${href} label=${this.localize.term('general_open')}></uui-button>
<uui-button @click=${() => this.#removeItem(item)} label=${this.localize.term('general_remove')}></uui-button>
</uui-action-bar>
</uui-ref-node-document-type>
`;
}
#renderIcon(item: UmbMediaTypeItemModel) {
if (!item.icon) return;
return html`<umb-icon slot="icon" name=${item.icon}></umb-icon>`;

View File

@@ -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<string | undefined, typeof UmbLitElement>(
@@ -124,6 +127,9 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
@state()
private _items?: Array<UmbMemberGroupItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#pickerContext = new UmbMemberGroupPickerInputContext(this);
constructor() {
@@ -152,6 +158,7 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
this.observe(this.#pickerContext.selection, (selection) => (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<string | und
});
}
#removeItem(item: UmbMemberGroupItemModel) {
this.#pickerContext.requestRemoveItem(item.unique);
#removeItem(unique: string) {
this.#pickerContext.requestRemoveItem(unique);
}
override render() {
@@ -173,13 +180,62 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
}
#renderItems() {
if (!this._items) return;
if (!this._statuses) return;
return html`
<uui-ref-list>
${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`
<umb-entity-item-ref
id=${unique}
?error=${true}
.errorMessage=${status.state.error}
.errorDetail=${unique}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(unique)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>
`;
}
// For successful items, use uui-ref-node
if (!item) return nothing;
return html`
<uui-ref-node
name=${item.name}
id=${unique}
href="${this._editMemberGroupPath}edit/${unique}"
?readonly=${this.readonly}>
<uui-action-bar slot="actions">
${when(
!this.readonly,
() =>
html`<uui-button
@click=${() => this.#removeItem(unique)}
label=${this.localize.term('general_remove')}></uui-button>`,
)}
</uui-action-bar>
<umb-icon slot="icon" name="icon-users"></umb-icon>
</uui-ref-node>
`;
},
)}
</uui-ref-list>
`;
@@ -199,27 +255,6 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
}
}
#renderItem(item: UmbMemberGroupItemModel) {
if (!item.unique) return nothing;
return html`
<uui-ref-node
name=${item.name}
id=${item.unique}
href="${this._editMemberGroupPath}edit/${item.unique}"
?readonly=${this.readonly}>
<uui-action-bar slot="actions"> ${this.#renderRemoveButton(item)} </uui-action-bar>
<umb-icon slot="icon" name="icon-users"></umb-icon>
</uui-ref-node>
`;
}
#renderRemoveButton(item: UmbMemberGroupItemModel) {
if (this.readonly) return nothing;
return html`<uui-button
@click=${() => this.#removeItem(item)}
label=${this.localize.term('general_remove')}></uui-button>`;
}
static override styles = [
css`
#btn-add {

View File

@@ -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 { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models';
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 '@umbraco-cms/backoffice/entity-item';
@customElement('umb-input-member-type')
export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
@@ -73,6 +76,9 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
@state()
private _items?: Array<UmbUniqueItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#pickerContext = new UmbMemberTypePickerInputContext(this);
constructor() {
@@ -92,6 +98,7 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
this.observe(this.#pickerContext.selection, (selection) => (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<string | unde
}
#renderItems() {
if (!this._items) return;
if (!this._statuses) return;
return html`
<uui-ref-list>
${repeat(
this._items,
(item) => item.unique,
(item) => this.#renderItem(item),
this._statuses,
(status) => status.unique,
(status) => this.#renderItem(status),
)}
</uui-ref-list>
`;
@@ -134,14 +141,37 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
`;
}
#renderItem(item: UmbUniqueItemModel) {
if (!item.unique) return;
#renderItem(status: UmbRepositoryItemsStatus) {
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`
<umb-entity-item-ref
id=${unique}
?error=${true}
.errorMessage=${status.state.error}
.errorDetail=${unique}
?standalone=${this.max === 1}>
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#pickerContext.requestRemoveItem(unique)}></uui-button>
</uui-action-bar>
</umb-entity-item-ref>
`;
}
// For successful items, use the member type specific component
if (!item?.unique) return nothing;
return html`
<uui-ref-node-document-type name=${this.localize.string(item.name)}>
${when(item.icon, () => html`<umb-icon slot="icon" name=${item.icon!}></umb-icon>`)}
<uui-action-bar slot="actions">
<uui-button
@click=${() => this.#pickerContext.requestRemoveItem(item.unique!)}
@click=${() => this.#pickerContext.requestRemoveItem(unique)}
label="Remove Member Type ${item.name}"
>${this.localize.term('general_remove')}</uui-button
>

View File

@@ -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<string | undefined, typeof UmbLitElement>(
@@ -121,6 +122,9 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
@state()
private _items?: Array<UmbMemberItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#pickerContext = new UmbMemberPickerInputContext(this);
constructor() {
@@ -140,6 +144,7 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
this.observe(this.#pickerContext.selection, (selection) => (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<string | undefine
);
}
#onRemove(item: UmbMemberItemModel) {
this.#pickerContext.requestRemoveItem(item.unique);
#onRemove(unique: string) {
this.#pickerContext.requestRemoveItem(unique);
}
override render() {
@@ -165,27 +170,43 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
}
#renderItems() {
if (!this._items) return nothing;
if (!this._statuses) return nothing;
return html`
<uui-ref-list>
${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`
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${isError}
.errorMessage=${status.state.error}
.errorDetail=${isError ? unique : undefined}
?readonly=${this.readonly}
?standalone=${this.max === 1}>
${when(
!this.readonly,
() => html`
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#onRemove(unique)}></uui-button>
</uui-action-bar>
`,
)}
</umb-entity-item-ref>
`;
},
)}
</uui-ref-list>
`;
}
#renderItem(item: UmbMemberItemModel) {
if (!item.unique) return nothing;
return html`
<umb-entity-item-ref id=${item.unique} .item=${item} ?readonly=${this.readonly} ?standalone=${this.max === 1}>
<uui-action-bar slot="actions">${this.#renderRemoveButton(item)} </uui-action-bar>
</umb-entity-item-ref>
`;
}
#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<string | undefine
}
}
#renderRemoveButton(item: UmbMemberItemModel) {
if (this.readonly) return nothing;
return html`
<uui-button @click=${() => this.#onRemove(item)} label=${this.localize.term('general_remove')}></uui-button>
`;
}
static override styles = [
css`
#btn-add {

View File

@@ -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;
}
}

View File

@@ -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<string | undefined, typeof UmbLitElement>(
@@ -79,6 +82,9 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
@state()
private _items?: Array<UmbStaticFileItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#pickerContext = new UmbStaticFilePickerInputContext(this);
constructor() {
@@ -98,6 +104,7 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
this.observe(this.#pickerContext.selection, (selection) => (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<string | unde
}
override render() {
if (!this._items) return nothing;
if (!this._statuses) return nothing;
return html`
<uui-ref-list>
${repeat(
this._items,
(item) => item.unique,
(item) => this._renderItem(item),
this._statuses,
(status) => status.unique,
(status) => this.#renderItem(status),
)}
</uui-ref-list>
${this.#renderAddButton()}
@@ -137,17 +144,25 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
`;
}
private _renderItem(item: UmbStaticFileItemModel) {
if (!item.unique) return;
#renderItem(status: UmbRepositoryItemsStatus) {
const unique = status.unique;
const item = this._items?.find((x) => x.unique === unique);
const isError = status.state.type === 'error';
return html`
<uui-ref-node name=${item.name} .detail=${this.#serializer.toServerPath(item.unique) || ''}>
<!-- TODO: implement is trashed, if we cant retrieve the item on the server (but only ask the server if we need to anyway...). <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> -->
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${isError}
.errorMessage=${status.state.error}
.errorDetail=${isError ? this.#serializer.toServerPath(unique) : undefined}
?standalone=${this.max === 1}>
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#pickerContext.requestRemoveItem(item.unique)}></uui-button>
@click=${() => this.#pickerContext.requestRemoveItem(unique)}></uui-button>
</uui-action-bar>
</uui-ref-node>
</umb-entity-item-ref>
`;
}

View File

@@ -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',

View File

@@ -73,6 +73,8 @@ export class UmbPropertyEditorUIStaticFilePickerElement extends UmbLitElement im
}
}
export { UmbPropertyEditorUIStaticFilePickerElement as element };
export default UmbPropertyEditorUIStaticFilePickerElement;
declare global {

View File

@@ -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 { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
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 { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
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<UmbUserGroupItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#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`
<uui-ref-list>${this._items?.map((item) => this._renderItem(item))}</uui-ref-list>
<uui-ref-list>
${this._statuses
? repeat(
this._statuses,
(status) => status.unique,
(status) => this.#renderItem(status),
)
: nothing}
</uui-ref-list>
<uui-button
id="btn-add"
look="placeholder"
@@ -123,15 +147,33 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
`;
}
private _renderItem(item: UmbUserGroupItemModel) {
if (!item.unique) return;
const href = `${this._editUserGroupPath}edit/${item.unique}`;
#renderItem(status: UmbRepositoryItemsStatus) {
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`
<umb-entity-item-ref id=${unique} ?error=${true} .errorMessage=${status.state.error} .errorDetail=${unique}>
<uui-action-bar slot="actions">
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#pickerContext.requestRemoveItem(unique)}></uui-button>
</uui-action-bar>
</umb-entity-item-ref>
`;
}
// For successful items, use umb-user-group-ref
if (!item?.unique) return nothing;
const href = `${this._editUserGroupPath}edit/${unique}`;
return html`
<umb-user-group-ref name="${ifDefined(item.name)}" href=${href}>
${item.icon ? html`<umb-icon slot="icon" name=${item.icon}></umb-icon>` : nothing}
<uui-action-bar slot="actions">
<uui-button
@click=${() => this.#pickerContext.requestRemoveItem(item.unique)}
@click=${() => this.#pickerContext.requestRemoveItem(unique)}
label=${this.localize.term('general_remove')}></uui-button>
</uui-action-bar>
</umb-user-group-ref>

View File

@@ -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<UmbUserItemModel>;
@state()
private _statuses?: Array<UmbRepositoryItemsStatus>;
#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`
<uui-ref-list>
${repeat(
this._items,
(item) => item.unique,
(item) => this.#renderItem(item),
this._statuses,
(status) => status.unique,
(status) => this.#renderItem(status),
)}
</uui-ref-list>
`;
}
#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`
<umb-entity-item-ref .item=${item} id=${item.unique} ?standalone=${this.max === 1}>
<umb-entity-item-ref
id=${unique}
.item=${item}
?error=${isError}
.errorMessage=${status.state.error}
.errorDetail=${isError ? unique : undefined}
?standalone=${this.max === 1}>
<uui-action-bar slot="actions">
<uui-button label=${this.localize.term('general_remove')} @click=${() => this.#removeItem(item)}></uui-button>
<uui-button
label=${this.localize.term('general_remove')}
@click=${() => this.#removeItem(unique)}></uui-button>
</uui-action-bar>
</umb-entity-item-ref>
`;