diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index 3daef92a88..5baba55f87 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -18,6 +18,7 @@ export * from './input-color/index.js'; export * from './input-content-type-property/index.js'; export * from './input-date/index.js'; export * from './input-dropdown/index.js'; +export * from './input-entity/index.js'; export * from './input-eye-dropper/index.js'; export * from './input-list-base/index.js'; export * from './input-manifest/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/index.ts new file mode 100644 index 0000000000..d175096a4b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/index.ts @@ -0,0 +1 @@ +export * from './input-entity.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts new file mode 100644 index 0000000000..192bfd17ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-entity/input-entity.element.ts @@ -0,0 +1,180 @@ +import { + css, + html, + customElement, + property, + state, + repeat, + ifDefined, + when, +} from '@umbraco-cms/backoffice/external/lit'; +import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; + +@customElement('umb-input-entity') +export class UmbInputEntityElement extends FormControlMixin(UmbLitElement) { + protected getFormElement() { + return undefined; + } + @property({ type: Number }) + public set min(value: number) { + if (this.#pickerContext) { + this.#pickerContext.min = value; + } + } + public get min(): number { + return this.#pickerContext?.min ?? 0; + } + + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + @property({ type: Number }) + public set max(value: number) { + if (this.#pickerContext) { + this.#pickerContext.max = value; + } + } + public get max(): number { + return this.#pickerContext?.max ?? Infinity; + } + + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + @property() + public set value(value: string) { + this.selection = splitStringToArray(value); + } + public get value(): string { + return this.selection?.join(',') ?? ''; + } + + public set pickerContext(ctor: new (host: UmbControllerHost) => UmbPickerInputContext) { + if (this.#pickerContext) return; + this.#pickerContext = new ctor(this); + this.#observePickerContext(); + } + public get pickerContext(): UmbPickerInputContext | undefined { + return this.#pickerContext; + } + #pickerContext?: UmbPickerInputContext; + + public set selection(value: Array) { + this.#pickerContext?.setSelection(value); + } + public get selection(): Array | undefined { + return this.#pickerContext?.getSelection(); + } + + @state() + // TODO: [LK] Find out if we can have a common interface for tree-picker entities, (rather than use `any`). + private _items?: Array; + + constructor() { + super(); + } + + // connectedCallback() { + // super.connectedCallback(); + + // if (!this.#pickerContext) return; + + // this.addValidator( + // 'rangeUnderflow', + // () => this.minMessage, + // () => !!this.min && this.#pickerContext.getSelection().length < this.min, + // ); + + // this.addValidator( + // 'rangeOverflow', + // () => this.maxMessage, + // () => !!this.max && this.#pickerContext.getSelection().length > this.max, + // ); + // } + + async #observePickerContext() { + if (!this.#pickerContext) return; + + this.observe( + this.#pickerContext.selection, + (selection) => (super.value = selection?.join(',') ?? ''), + 'observeSelection', + ); + + this.observe( + this.#pickerContext.selectedItems, + (selectedItems) => { + this._items = selectedItems; + }, + 'observeSelectedItems', + ); + } + + #openPicker() { + this.#pickerContext?.openPicker({ + hideTreeRoot: true, + }); + } + + render() { + return html` ${this.#renderItems()} ${this.#renderAddButton()} `; + } + + #renderAddButton() { + if (this.max === 1 && this.selection && this.selection.length >= this.max) return; + return html` + + `; + } + + #renderItems() { + if (!this._items) return; + return html` + + ${repeat( + this._items, + (item) => item.unique, + (item) => this.#renderItem(item), + )} + + `; + } + + #renderItem(item: any) { + if (!item.unique) return; + return html` + + ${when(item.icon, () => html``)} + + this.#pickerContext?.requestRemoveItem(item.unique)} + label=${this.localize.term('general_remove')}> + + + `; + } + + static styles = [ + css` + #btn-add { + width: 100%; + } + `, + ]; +} + +export default UmbInputEntityElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-entity': UmbInputEntityElement; + } +} 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 6c07396806..99ac21aa30 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 @@ -1,9 +1,10 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { type UmbItemRepository, UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; +import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository'; +import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; export class UmbPickerInputContext extends UmbControllerBase { // TODO: We are way too unsecure about the requirements for the Modal Token, as we have certain expectation for the data and value.