reuse generic picker logic for input-section
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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<UmbSectionItemModel> {
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_SECTION_ITEM_REPOSITORY_ALIAS, UMB_SECTION_PICKER_MODAL);
|
||||
}
|
||||
}
|
||||
@@ -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<string> {
|
||||
return this.#pickerContext.getSelection();
|
||||
}
|
||||
public set selection(uniques: Array<string>) {
|
||||
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<ManifestSection> = [];
|
||||
private _items?: Array<UmbSectionItemModel>;
|
||||
|
||||
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<ManifestSection>) => {
|
||||
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`
|
||||
<div id="user-list">
|
||||
${this._sections.map(
|
||||
(section) => html`
|
||||
<div class="user-group">
|
||||
<div>
|
||||
<span>${section.meta.label}</span>
|
||||
</div>
|
||||
<uui-button
|
||||
@click=${() => this.removeFromSelection(section.alias)}
|
||||
label="remove"
|
||||
color="danger"></uui-button>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
<uui-ref-list>${this._items?.map((item) => this._renderItem(item))}</uui-ref-list>
|
||||
<uui-button id="add-button" look="placeholder" @click=${() => this.#pickerContext.openPicker()} label="open"
|
||||
>Add</uui-button
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItem(item: UmbSectionItemModel) {
|
||||
if (!item.unique) return;
|
||||
return html`${item.unique}
|
||||
<!--
|
||||
<uui-ref-node-data-type name=${item.name}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(item.unique)}
|
||||
label="Remove Data Type ${item.name}"
|
||||
>Remove</uui-button
|
||||
>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node-data-type>
|
||||
-->`;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -13,14 +13,24 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement<
|
||||
@state()
|
||||
private _sections: Array<ManifestSection> = [];
|
||||
|
||||
@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`
|
||||
<uui-menu-item
|
||||
label=${item.meta.label}
|
||||
selectable
|
||||
?selectable=${this._selectable}
|
||||
?selected=${this.#selectionManager.isSelected(item.alias)}
|
||||
@selected=${() => this.#selectionManager.select(item.alias)}
|
||||
@deselected=${() => this.#selectionManager.deselect(item.alias)}></uui-menu-item>
|
||||
Reference in New Issue
Block a user