diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts index bd007f31f0..d60293a578 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts @@ -6,4 +6,4 @@ export * from './section-sidebar-menu-with-entity-actions/index.js'; export * from './section-default.element.js'; export * from './section.context.js'; export * from './input-section/index.js'; -export * from './section-picker/section-picker-modal.token.js'; +export * from './section-picker-modal/section-picker-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.context.ts new file mode 100644 index 0000000000..5a0a3be146 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.context.ts @@ -0,0 +1,11 @@ +import type { UmbSectionItemModel } from '../repository/index.js'; +import { UMB_SECTION_ITEM_REPOSITORY_ALIAS } from '../repository/index.js'; +import { UMB_SECTION_PICKER_MODAL } from '../section-picker-modal/section-picker-modal.token.js'; +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbSectionPickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHost) { + super(host, UMB_SECTION_ITEM_REPOSITORY_ALIAS, UMB_SECTION_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.element.ts index 99ac8aa0d0..c756c38f03 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/input-section/input-section.element.ts @@ -1,89 +1,137 @@ -import { UmbInputListBaseElement } from '../../components/input-list-base/input-list-base.js'; -import { UMB_SECTION_PICKER_MODAL } from '../section-picker/section-picker-modal.token.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import type { ManifestSection } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbSectionItemModel } from '../repository/index.js'; +import { UmbSectionPickerContext } from './input-section.context.js'; +import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; @customElement('umb-input-section') -export class UmbInputSectionElement extends UmbInputListBaseElement { +export class UmbInputSectionElement extends FormControlMixin(UmbLitElement) { + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default 0 + */ + @property({ type: Number }) + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default Infinity + */ + @property({ type: Number }) + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + public get selection(): Array { + return this.#pickerContext.getSelection(); + } + public set selection(uniques: Array) { + this.#pickerContext.setSelection(uniques); + } + + @property() + public set value(selectionString: string) { + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + if (typeof selectionString !== 'string') return; + if (selectionString === this.value) return; + this.selection = splitStringToArray(selectionString); + } + @state() - private _sections: Array = []; + private _items?: Array; - connectedCallback(): void { - super.connectedCallback(); - this.pickerToken = UMB_SECTION_PICKER_MODAL; - this._observeSections(); + #pickerContext = new UmbSectionPickerContext(this); + + constructor() { + super(); + + 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, + ); + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } - private _observeSections() { - if (this.value.length > 0) { - this.observe(umbExtensionsRegistry.byType('section'), (sections: Array) => { - this._sections = sections.filter((section) => this.value.includes(section.alias)); - }); - } else { - this._sections = []; - } + protected getFormElement() { + return undefined; } - selectionUpdated() { - this._observeSections(); - // TODO: Use proper event class: - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); - } - - renderContent() { - if (this._sections.length === 0) return html`${nothing}`; - + render() { return html` -
- ${this._sections.map( - (section) => html` -
-
- ${section.meta.label} -
- this.removeFromSelection(section.alias)} - label="remove" - color="danger"> -
- `, - )} -
+ ${this._items?.map((item) => this._renderItem(item))} + this.#pickerContext.openPicker()} label="open" + >Add `; } + private _renderItem(item: UmbSectionItemModel) { + if (!item.unique) return; + return html`${item.unique} + `; + } + static styles = [ - UmbTextStyles, css` - :host { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - } - #user-group-list { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - } - .user-group { - display: flex; - align-items: center; - gap: var(--uui-size-space-2); - } - .user-group div { - display: flex; - align-items: center; - gap: var(--uui-size-4); - } - .user-group uui-button { - margin-left: auto; + #add-button { + width: 100%; } `, ]; } +export default UmbInputSectionElement; + declare global { interface HTMLElementTagNameMap { 'umb-input-section': UmbInputSectionElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/manifests.ts index 90eff35d58..718e97f9eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/manifests.ts @@ -1,8 +1,11 @@ +import { manifests as repositoryManifests } from './repository/manifests.js'; + export const manifests = [ { type: 'modal', alias: 'Umb.Modal.SectionPicker', name: 'Section Picker Modal', - js: () => import('./section-picker/section-picker-modal.element.js'), + js: () => import('./section-picker-modal/section-picker-modal.element.js'), }, + ...repositoryManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker-modal.element.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker-modal.element.ts index 5a4061c4e1..6cc044f303 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker-modal.element.ts @@ -13,14 +13,24 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement< @state() private _sections: Array = []; + @state() + private _selectable = false; + #selectionManager = new UmbSelectionManager(this); + constructor() { + super(); + + this.observe(this.#selectionManager.selectable, (selectable) => (this._selectable = selectable)); + } + connectedCallback(): void { super.connectedCallback(); // TODO: in theory this config could change during the lifetime of the modal, so we could observe it this.#selectionManager.setMultiple(this.data?.multiple ?? false); this.#selectionManager.setSelection(this.data?.selection ?? []); + this.#selectionManager.setSelectable(true); this.observe( umbExtensionsRegistry.byType('section'), @@ -42,7 +52,7 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement< (item) => html` this.#selectionManager.select(item.alias)} @deselected=${() => this.#selectionManager.deselect(item.alias)}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker-modal.token.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker-modal.token.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker.test.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker/section-picker.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/section-picker-modal/section-picker.test.ts