Merge branch 'main' into improvement/model-remapping
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Property Dataset Dashboard Example
|
||||
|
||||
This example demonstrates how to set up the Sorter and how it can be used in nested setups.
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [
|
||||
{
|
||||
type: 'dashboard',
|
||||
name: 'Example Sorter Dashboard',
|
||||
alias: 'example.dashboard.dataset',
|
||||
element: () => import('./sorter-dashboard.js'),
|
||||
weight: 900,
|
||||
meta: {
|
||||
label: 'Sorter example',
|
||||
pathname: 'sorter-example',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,89 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
|
||||
import type { ModelEntryType } from './sorter-group.js';
|
||||
|
||||
import './sorter-group.js';
|
||||
@customElement('example-sorter-dashboard')
|
||||
export class ExampleSorterDashboard extends UmbElementMixin(LitElement) {
|
||||
groupOneItems: ModelEntryType[] = [
|
||||
{
|
||||
name: 'Apple',
|
||||
children: [
|
||||
{
|
||||
name: 'Juice',
|
||||
},
|
||||
{
|
||||
name: 'Milk',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Banana',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
name: 'Pear',
|
||||
},
|
||||
{
|
||||
name: 'Pineapple',
|
||||
},
|
||||
{
|
||||
name: 'Lemon',
|
||||
children: [
|
||||
{
|
||||
name: 'Cola',
|
||||
},
|
||||
{
|
||||
name: 'Pepsi',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
groupTwoItems: ModelEntryType[] = [
|
||||
{
|
||||
name: 'DXP',
|
||||
},
|
||||
{
|
||||
name: 'H5YR',
|
||||
},
|
||||
{
|
||||
name: 'UUI',
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box class="uui-text">
|
||||
<div class="outer-wrapper">
|
||||
<h5>Notice this example still only support single group of Sorter.</h5>
|
||||
<example-sorter-group .initialItems=${this.groupOneItems}></example-sorter-group>
|
||||
<example-sorter-group .initialItems=${this.groupTwoItems}></example-sorter-group>
|
||||
</div>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--uui-size-layout-1);
|
||||
}
|
||||
|
||||
.outer-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default ExampleSorterDashboard;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-sorter-dashboard-nested': ExampleSorterDashboard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, LitElement, repeat, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
|
||||
import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
import './sorter-item.js';
|
||||
import ExampleSorterItem from './sorter-item.js';
|
||||
|
||||
export type ModelEntryType = {
|
||||
name: string;
|
||||
children?: ModelEntryType[];
|
||||
};
|
||||
|
||||
@customElement('example-sorter-group')
|
||||
export class ExampleSorterGroup extends UmbElementMixin(LitElement) {
|
||||
@property({ type: Array, attribute: false })
|
||||
public get initialItems(): ModelEntryType[] {
|
||||
return this.items;
|
||||
}
|
||||
public set initialItems(value: ModelEntryType[]) {
|
||||
// Only want to set the model initially, cause any re-render should not set this again.
|
||||
if (this._items !== undefined) return;
|
||||
this.items = value;
|
||||
}
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
public get items(): ModelEntryType[] {
|
||||
return this._items ?? [];
|
||||
}
|
||||
public set items(value: ModelEntryType[]) {
|
||||
const oldValue = this._items;
|
||||
this._items = value;
|
||||
this.#sorter.setModel(this._items);
|
||||
this.requestUpdate('items', oldValue);
|
||||
}
|
||||
private _items?: ModelEntryType[];
|
||||
|
||||
#sorter = new UmbSorterController<ModelEntryType, ExampleSorterItem>(this, {
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.name;
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.name;
|
||||
},
|
||||
identifier: 'string-that-identifies-all-example-sorters',
|
||||
itemSelector: 'example-sorter-item',
|
||||
containerSelector: '.sorter-container',
|
||||
onChange: ({ model }) => {
|
||||
const oldValue = this._items;
|
||||
this._items = model;
|
||||
this.requestUpdate('items', oldValue);
|
||||
},
|
||||
});
|
||||
|
||||
removeItem = (item: ModelEntryType) => {
|
||||
this.items = this.items.filter((r) => r.name !== item.name);
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="sorter-container">
|
||||
${repeat(
|
||||
this.items,
|
||||
(item) => item.name,
|
||||
(item) =>
|
||||
html`<example-sorter-item name=${item.name}>
|
||||
<button slot="action" @click=${() => this.removeItem(item)}>Delete</button>
|
||||
${item.children ? html`<example-sorter-group .initialItems=${item.children}></example-sorter-group>` : ''}
|
||||
</example-sorter-item>`,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sorter-placeholder {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.sorter-container {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
example-sorter-group {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px dashed rgba(122, 122, 122, 0.25);
|
||||
border-radius: calc(var(--uui-border-radius) * 2);
|
||||
padding: var(--uui-size-space-1);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default ExampleSorterGroup;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-sorter-group-nested': ExampleSorterGroup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, LitElement, state, repeat, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
|
||||
import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
@customElement('example-sorter-item')
|
||||
export class ExampleSorterItem extends UmbElementMixin(LitElement) {
|
||||
@property({ type: String, reflect: true })
|
||||
name: string = '';
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: 'drag-placeholder' })
|
||||
umbDragPlaceholder = false;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div>
|
||||
${this.name}
|
||||
<img src="https://picsum.photos/seed/${this.name}/400/400" style="width:120px;" />
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--uui-size-layout-1);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
border-radius: var(--uui-border-radius);
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
:host([drag-placeholder]) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
slot:not([name]) {
|
||||
// go on new line:
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default ExampleSorterItem;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-sorter-item-nested': ExampleSorterItem;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
# Property Dataset Dashboard Example
|
||||
|
||||
This example demonstrates the how to setup the Sorter.
|
||||
|
||||
This example can still NOT sort between two groups. This will come later.
|
||||
This example demonstrates how to set up the Sorter, and how it can be linked with another Sorter.
|
||||
|
||||
@@ -40,7 +40,6 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) {
|
||||
return html`
|
||||
<uui-box class="uui-text">
|
||||
<div class="outer-wrapper">
|
||||
<h5>Notice this example still only support single group of Sorter.</h5>
|
||||
<example-sorter-group .items=${this.groupOneItems}></example-sorter-group>
|
||||
<example-sorter-group .items=${this.groupTwoItems}></example-sorter-group>
|
||||
</div>
|
||||
|
||||
@@ -10,73 +10,42 @@ export type ModelEntryType = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<ModelEntryType, ExampleSorterItem> = {
|
||||
compareElementToModel: (element, model) => {
|
||||
return element.name === model.name;
|
||||
},
|
||||
querySelectModelToElement: (container, modelEntry) => {
|
||||
return container.querySelector("example-sorter-item[name='" + modelEntry.name + "']");
|
||||
},
|
||||
identifier: 'string-that-identifies-all-example-sorters',
|
||||
itemSelector: 'example-sorter-item',
|
||||
containerSelector: '.sorter-container',
|
||||
};
|
||||
|
||||
@customElement('example-sorter-group')
|
||||
export class ExampleSorterGroup extends UmbElementMixin(LitElement) {
|
||||
//
|
||||
// Property that is used to set the model of the sorter.
|
||||
@property({ type: Array, attribute: false })
|
||||
public get items(): ModelEntryType[] {
|
||||
return this._items;
|
||||
return this._items ?? [];
|
||||
}
|
||||
public set items(value: ModelEntryType[]) {
|
||||
// Only want to set the model initially via this one, as this is the initial model, cause any re-render should not set this data again.
|
||||
if (this._items !== undefined) return;
|
||||
this._items = value;
|
||||
this.#sorter.setModel(this._items);
|
||||
}
|
||||
private _items: ModelEntryType[] = [];
|
||||
private _items?: ModelEntryType[];
|
||||
|
||||
#sorter = new UmbSorterController<ModelEntryType, ExampleSorterItem>(this, {
|
||||
...SORTER_CONFIG,
|
||||
/*performItemInsert: ({ item, newIndex }) => {
|
||||
const oldValue = this._items;
|
||||
//console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', '));
|
||||
const newItems = [...this._items];
|
||||
newItems.splice(newIndex, 0, item);
|
||||
this.items = newItems;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
return true;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.name;
|
||||
},
|
||||
performItemRemove: ({ item }) => {
|
||||
const oldValue = this._items;
|
||||
//console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', '));
|
||||
const indexToMove = this._items.findIndex((x) => x.name === item.name);
|
||||
const newItems = [...this._items];
|
||||
newItems.splice(indexToMove, 1);
|
||||
this.items = newItems;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
return true;
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.name;
|
||||
},
|
||||
performItemMove: ({ item, newIndex, oldIndex }) => {
|
||||
const oldValue = this._items;
|
||||
//console.log('move', item.name, 'from', oldIndex, 'to', newIndex, ' ', this._items.map((x) => x.name).join(', '));
|
||||
const newItems = [...this._items];
|
||||
newItems.splice(oldIndex, 1);
|
||||
if (oldIndex <= newIndex) {
|
||||
newIndex--;
|
||||
}
|
||||
newItems.splice(newIndex, 0, item);
|
||||
this.items = newItems;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
return true;
|
||||
},*/
|
||||
identifier: 'string-that-identifies-all-example-sorters',
|
||||
itemSelector: 'example-sorter-item',
|
||||
containerSelector: '.sorter-container',
|
||||
onChange: ({ model }) => {
|
||||
const oldValue = this._items;
|
||||
this._items = model;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
this.requestUpdate('items', oldValue);
|
||||
},
|
||||
});
|
||||
|
||||
removeItem = (item: ModelEntryType) => {
|
||||
this.items = this._items.filter((r) => r.name !== item.name);
|
||||
this._items = this._items!.filter((r) => r.name !== item.name);
|
||||
this.#sorter.setModel(this._items);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -87,7 +56,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) {
|
||||
(item) => item.name,
|
||||
(item) =>
|
||||
html`<example-sorter-item name=${item.name}>
|
||||
<button @click=${() => this.removeItem(item)}>Delete</button>
|
||||
<button slot="action" @click=${() => this.removeItem(item)}>Delete</button>
|
||||
</example-sorter-item>`,
|
||||
)}
|
||||
</div>
|
||||
@@ -105,6 +74,10 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) {
|
||||
.sorter-placeholder {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.sorter-container {
|
||||
min-height: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.name}
|
||||
<img src="https://picsum.photos/seed/${this.name}/400/400" style="width:120px;" />
|
||||
<div>
|
||||
${this.name}
|
||||
<img src="https://picsum.photos/seed/${this.name}/400/400" style="width:120px;" />
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
@@ -23,9 +26,7 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) {
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
display: block;
|
||||
padding: var(--uui-size-layout-1);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
border-radius: var(--uui-border-radius);
|
||||
@@ -34,6 +35,16 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) {
|
||||
:host([drag-placeholder]) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
slot:not([name]) {
|
||||
// go on new line:
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {}
|
||||
export interface UmbBlockListValueModel extends UmbBlockValueType<UmbBlockListLayoutModel> {}
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbBlockListLayoutModel, UmbPropertyEditorUIBlockListBlockElement> = {
|
||||
compareElementToModel: (element, model) => {
|
||||
return element.getAttribute('data-udi') === model.contentUdi;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-udi');
|
||||
},
|
||||
querySelectModelToElement: (container, modelEntry) => {
|
||||
return container.querySelector("umb-property-editor-ui-block-list-block[data-udi='" + modelEntry.contentUdi + "']");
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.contentUdi;
|
||||
},
|
||||
identifier: 'block-list-editor',
|
||||
itemSelector: 'umb-property-editor-ui-block-list-block',
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { CSSResultGroup} from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, repeat, query } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type {
|
||||
UmbNotificationHandler,
|
||||
UmbNotificationContext} from '@umbraco-cms/backoffice/notification';
|
||||
import {
|
||||
UMB_NOTIFICATION_CONTEXT,
|
||||
} from '@umbraco-cms/backoffice/notification';
|
||||
import type { UmbNotificationHandler, UmbNotificationContext } from '@umbraco-cms/backoffice/notification';
|
||||
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-backoffice-notification-container')
|
||||
@@ -38,11 +34,11 @@ export class UmbBackofficeNotificationContainerElement extends UmbLitElement {
|
||||
// TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this._notificationsElement?.hidePopover();
|
||||
this._notificationsElement?.hidePopover?.(); // To prevent issues in FireFox I added `?.` before `()` [NL]
|
||||
// TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this._notificationsElement?.showPopover();
|
||||
this._notificationsElement?.showPopover?.(); // To prevent issues in FireFox I added `?.` before `()` [NL]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbSwatchDetails> = {
|
||||
compareElementToModel: (element: HTMLElement, model: UmbSwatchDetails) => {
|
||||
return element.getAttribute('data-sort-entry-id') === model.value;
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbSwatchDetails, UmbMultipleColorPickerItemInputElement> = {
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.value.toString();
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: UmbSwatchDetails) => {
|
||||
return container.querySelector('[data-sort-entry-id=' + modelEntry.value + ']');
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.value;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.ColorEditor',
|
||||
itemSelector: 'umb-multiple-color-picker-item-input',
|
||||
@@ -192,7 +192,6 @@ export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitE
|
||||
html` <umb-multiple-color-picker-item-input
|
||||
?showLabels=${this.showLabels}
|
||||
value=${item.value}
|
||||
data-sort-entry-id=${item.value}
|
||||
label=${ifDefined(item.label)}
|
||||
name="item-${index}"
|
||||
@change=${(event: UmbChangeEvent) => this.#onChange(event, index)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UmbSorterConfig} from '@umbraco-cms/backoffice/sorter';
|
||||
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
export type MultipleTextStringValue = Array<MultipleTextStringValueItem>;
|
||||
@@ -14,11 +14,11 @@ export interface MultipleTextStringValueItem {
|
||||
}
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<MultipleTextStringValueItem> = {
|
||||
compareElementToModel: (element: HTMLElement, model: MultipleTextStringValueItem) => {
|
||||
return element.getAttribute('data-sort-entry-id') === model.value;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-sort-entry-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: MultipleTextStringValueItem) => {
|
||||
return container.querySelector('[data-sort-entry-id=' + modelEntry.value + ']');
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.value;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.ColorEditor',
|
||||
itemSelector: 'umb-input-multiple-text-string-item',
|
||||
|
||||
@@ -40,10 +40,6 @@ function getParentScrollElement(el: Element, includeSelf: boolean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function preventDragOver(e: Event) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function setupIgnorerElements(element: HTMLElement, ignorerSelectors: string) {
|
||||
ignorerSelectors.split(',').forEach(function (criteria) {
|
||||
element.querySelectorAll(criteria.trim()).forEach(setupPreventEvent);
|
||||
@@ -62,9 +58,9 @@ function destroyPreventEvent(element: Element) {
|
||||
}
|
||||
|
||||
type INTERNAL_UmbSorterConfig<T, ElementType extends HTMLElement> = {
|
||||
compareElementToModel: (el: ElementType, modelEntry: T) => boolean;
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: T) => ElementType | null;
|
||||
identifier: string;
|
||||
getUniqueOfElement: (element: ElementType) => string | null | symbol | number;
|
||||
getUniqueOfModel: (modeEntry: T) => string | null | symbol | number;
|
||||
identifier: string | symbol;
|
||||
itemSelector: string;
|
||||
disabledItemSelector?: string;
|
||||
containerSelector: string;
|
||||
@@ -101,9 +97,9 @@ type INTERNAL_UmbSorterConfig<T, ElementType extends HTMLElement> = {
|
||||
// External type with some properties optional, as they have defaults:
|
||||
export type UmbSorterConfig<T, ElementType extends HTMLElement = HTMLElement> = Omit<
|
||||
INTERNAL_UmbSorterConfig<T, ElementType>,
|
||||
'ignorerSelector' | 'containerSelector'
|
||||
'ignorerSelector' | 'containerSelector' | 'identifier'
|
||||
> &
|
||||
Partial<Pick<INTERNAL_UmbSorterConfig<T, ElementType>, 'ignorerSelector' | 'containerSelector'>>;
|
||||
Partial<Pick<INTERNAL_UmbSorterConfig<T, ElementType>, 'ignorerSelector' | 'containerSelector' | 'identifier'>>;
|
||||
|
||||
/**
|
||||
* @export
|
||||
@@ -112,6 +108,23 @@ export type UmbSorterConfig<T, ElementType extends HTMLElement = HTMLElement> =
|
||||
* @description This controller can make user able to sort items.
|
||||
*/
|
||||
export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElement> implements UmbController {
|
||||
//
|
||||
// A sorter that is requested to become the next sorter:
|
||||
static originalSorter?: UmbSorterController<unknown>;
|
||||
static originalIndex?: number;
|
||||
|
||||
// A sorter that is requested to become the next sorter:
|
||||
static dropSorter?: UmbSorterController<unknown>;
|
||||
|
||||
// The sorter of which the element is located within:
|
||||
static activeSorter?: UmbSorterController<unknown>;
|
||||
|
||||
// Information about the current dragged item/element:
|
||||
static activeIndex?: number;
|
||||
static activeItem?: any;
|
||||
static activeElement?: HTMLElement;
|
||||
static activeDragElement?: Element;
|
||||
|
||||
#host;
|
||||
#config: INTERNAL_UmbSorterConfig<T, ElementType>;
|
||||
#observer;
|
||||
@@ -120,24 +133,20 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
#rqaId?: number;
|
||||
|
||||
#containerElement!: HTMLElement;
|
||||
|
||||
#currentContainerCtrl: UmbSorterController<T, ElementType> = this;
|
||||
#currentContainerElement: Element | null = null;
|
||||
#useContainerShadowRoot?: boolean;
|
||||
|
||||
#scrollElement?: Element | null;
|
||||
#currentElement?: ElementType;
|
||||
#currentDragElement?: Element;
|
||||
#currentDragRect?: DOMRect;
|
||||
#currentItem?: T;
|
||||
|
||||
#currentIndex?: number;
|
||||
#dragX = 0;
|
||||
#dragY = 0;
|
||||
|
||||
#lastIndicationContainerCtrl: UmbSorterController<T, ElementType> | null = null;
|
||||
|
||||
public get controllerAlias() {
|
||||
// We only support one Sorter Controller pr. Controller Host.
|
||||
return 'umbSorterController';
|
||||
}
|
||||
public get identifier() {
|
||||
return this.#config.identifier;
|
||||
}
|
||||
|
||||
@@ -145,6 +154,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#host = host;
|
||||
|
||||
// Set defaults:
|
||||
config.identifier ??= Symbol();
|
||||
config.ignorerSelector ??= 'a, img, iframe';
|
||||
if (!config.placeholderClass && !config.placeholderAttr) {
|
||||
config.placeholderAttr = 'drag-placeholder';
|
||||
@@ -176,6 +186,14 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#model = model;
|
||||
}
|
||||
|
||||
hasItem(unique: string) {
|
||||
return this.#model.find((x) => this.#config.getUniqueOfModel(x) === unique) !== undefined;
|
||||
}
|
||||
|
||||
getItem(unique: string) {
|
||||
return this.#model.find((x) => this.#config.getUniqueOfModel(x) === unique);
|
||||
}
|
||||
|
||||
hostConnected() {
|
||||
requestAnimationFrame(this._onFirstRender);
|
||||
}
|
||||
@@ -188,19 +206,13 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#containerElement = containerEl as HTMLElement;
|
||||
this.#useContainerShadowRoot = this.#containerElement === this.#host;
|
||||
|
||||
if (!this.#currentContainerElement || this.#currentContainerElement === this.#containerElement) {
|
||||
this.#currentContainerElement = containerEl;
|
||||
}
|
||||
|
||||
// Only look at the shadowRoot if the containerElement is host.
|
||||
const containerElement = this.#useContainerShadowRoot
|
||||
? this.#containerElement.shadowRoot ?? this.#containerElement
|
||||
: this.#containerElement;
|
||||
containerElement.addEventListener('dragover', preventDragOver);
|
||||
containerElement.addEventListener('dragover', this._itemDraggedOver as unknown as EventListener);
|
||||
|
||||
(this.#containerElement as any)['__umbBlockGridSorterController'] = () => {
|
||||
return this;
|
||||
};
|
||||
// TODO: Do we need to handle dragleave?
|
||||
|
||||
this.#observer.disconnect();
|
||||
|
||||
@@ -215,15 +227,50 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
});
|
||||
};
|
||||
hostDisconnected() {
|
||||
// TODO: Clean up??
|
||||
// TODO: Is there more clean up to do??
|
||||
this.#observer.disconnect();
|
||||
if (this.#containerElement) {
|
||||
(this.#containerElement as any)['__umbBlockGridSorterController'] = undefined;
|
||||
this.#containerElement.removeEventListener('dragover', preventDragOver);
|
||||
// Only look at the shadowRoot if the containerElement is host.
|
||||
const containerElement = this.#useContainerShadowRoot
|
||||
? this.#containerElement.shadowRoot ?? this.#containerElement
|
||||
: this.#containerElement;
|
||||
|
||||
containerElement.removeEventListener('dragover', this._itemDraggedOver as unknown as EventListener);
|
||||
(this.#containerElement as any) = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
_itemDraggedOver = (e: DragEvent) => {
|
||||
//if(UmbSorterController.activeSorter === this) return;
|
||||
const dropSorter = UmbSorterController.dropSorter as unknown as UmbSorterController<T, ElementType>;
|
||||
if (!dropSorter || dropSorter.identifier !== this.identifier) return;
|
||||
|
||||
if (dropSorter === this) {
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
|
||||
// Do nothing as we are the active sorter.
|
||||
this.#handleDragMove(e);
|
||||
|
||||
// Maybe we need to stop the event in this case.
|
||||
|
||||
// Do not bubble up to parent sorters:
|
||||
e.stopPropagation();
|
||||
|
||||
return;
|
||||
} else {
|
||||
// TODO: Check if dropping here is okay..
|
||||
|
||||
// If so lets set the approaching sorter:
|
||||
UmbSorterController.dropSorter = this as unknown as UmbSorterController<unknown>;
|
||||
|
||||
// Do not bubble up to parent sorters:
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
setupItem(element: ElementType) {
|
||||
if (this.#config.ignorerSelector) {
|
||||
setupIgnorerElements(element, this.#config.ignorerSelector);
|
||||
@@ -232,12 +279,15 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
if (!this.#config.disabledItemSelector || !element.matches(this.#config.disabledItemSelector)) {
|
||||
element.draggable = true;
|
||||
element.addEventListener('dragstart', this.#handleDragStart);
|
||||
element.addEventListener('dragend', this.#handleDragEnd);
|
||||
}
|
||||
|
||||
// If we have a currentItem and the element matches, we should set the currentElement to this element.
|
||||
if (this.#currentItem && this.#config.compareElementToModel(element, this.#currentItem)) {
|
||||
if (this.#currentElement !== element) {
|
||||
console.log('THIS ACTUALLY HAPPENED... NOTICE THIS!');
|
||||
if (
|
||||
UmbSorterController.activeItem &&
|
||||
this.#config.getUniqueOfElement(element) === this.#config.getUniqueOfModel(UmbSorterController.activeItem)
|
||||
) {
|
||||
if (UmbSorterController.activeElement !== element) {
|
||||
this.#setCurrentElement(element);
|
||||
}
|
||||
}
|
||||
@@ -249,33 +299,36 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
}
|
||||
|
||||
element.removeEventListener('dragstart', this.#handleDragStart);
|
||||
// We are not ready to remove the dragend or drop, as this is might be the active one just moving container:
|
||||
//element.removeEventListener('dragend', this.#handleDragEnd);
|
||||
//element.addEventListener('drop', this.#handleDrop);
|
||||
}
|
||||
|
||||
#setupPlaceholderStyle() {
|
||||
if (this.#config.placeholderClass) {
|
||||
this.#currentElement?.classList.add(this.#config.placeholderClass);
|
||||
UmbSorterController.activeElement?.classList.add(this.#config.placeholderClass);
|
||||
}
|
||||
if (this.#config.placeholderAttr) {
|
||||
this.#currentElement?.setAttribute(this.#config.placeholderAttr, '');
|
||||
UmbSorterController.activeElement?.setAttribute(this.#config.placeholderAttr, '');
|
||||
}
|
||||
}
|
||||
#removePlaceholderStyle() {
|
||||
if (this.#config.placeholderClass) {
|
||||
this.#currentElement?.classList.remove(this.#config.placeholderClass);
|
||||
UmbSorterController.activeElement?.classList.remove(this.#config.placeholderClass);
|
||||
}
|
||||
if (this.#config.placeholderAttr) {
|
||||
this.#currentElement?.removeAttribute(this.#config.placeholderAttr);
|
||||
UmbSorterController.activeElement?.removeAttribute(this.#config.placeholderAttr);
|
||||
}
|
||||
}
|
||||
|
||||
#setCurrentElement(element: ElementType) {
|
||||
this.#currentElement = element;
|
||||
UmbSorterController.activeElement = element;
|
||||
|
||||
this.#currentDragElement = this.#config.draggableSelector
|
||||
UmbSorterController.activeDragElement = this.#config.draggableSelector
|
||||
? element.querySelector(this.#config.draggableSelector) ?? undefined
|
||||
: element;
|
||||
|
||||
if (!this.#currentDragElement) {
|
||||
if (!UmbSorterController.activeDragElement) {
|
||||
throw new Error(
|
||||
'Could not find drag element, query was made with the `draggableSelector` of "' +
|
||||
this.#config.draggableSelector +
|
||||
@@ -291,14 +344,15 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
const element = (event.target as HTMLElement).closest(this.#config.itemSelector);
|
||||
if (!element) return;
|
||||
|
||||
if (this.#currentElement && this.#currentElement !== element) {
|
||||
if (UmbSorterController.activeElement && UmbSorterController.activeElement !== element) {
|
||||
// TODO: Remove this console log at one point.
|
||||
console.log("drag start realized that something was already active, so we'll end it. -------!!!!#€#%#€");
|
||||
this.#handleDragEnd();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move'; // copyMove when we enhance the drag with clipboard data.
|
||||
event.dataTransfer.dropEffect = 'none'; // visual feedback when dropped.
|
||||
event.dataTransfer.effectAllowed = 'all'; // copyMove when we enhance the drag with clipboard data.// defaults to 'all'
|
||||
}
|
||||
|
||||
if (!this.#scrollElement) {
|
||||
@@ -306,56 +360,74 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
}
|
||||
|
||||
this.#setCurrentElement(element as ElementType);
|
||||
this.#currentDragRect = this.#currentDragElement?.getBoundingClientRect();
|
||||
this.#currentItem = this.getItemOfElement(this.#currentElement!);
|
||||
if (!this.#currentItem) {
|
||||
UmbSorterController.activeItem = this.getItemOfElement(UmbSorterController.activeElement! as ElementType);
|
||||
|
||||
UmbSorterController.originalSorter = this as unknown as UmbSorterController<unknown>;
|
||||
UmbSorterController.originalIndex = this.#model.indexOf(UmbSorterController.activeItem);
|
||||
|
||||
if (!UmbSorterController.activeItem) {
|
||||
console.error('Could not find item related to this element.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current index of the item:
|
||||
this.#currentIndex = this.#model.indexOf(this.#currentItem);
|
||||
UmbSorterController.activeIndex = this.#model.indexOf(UmbSorterController.activeItem as T);
|
||||
|
||||
this.#currentElement!.style.transform = 'translateZ(0)'; // Solves problem with FireFox and ShadowDom in the drag-image.
|
||||
UmbSorterController.activeElement!.style.transform = 'translateZ(0)'; // Solves problem with FireFox and ShadowDom in the drag-image.
|
||||
|
||||
if (this.#config.dataTransferResolver) {
|
||||
this.#config.dataTransferResolver(event.dataTransfer, this.#currentItem);
|
||||
this.#config.dataTransferResolver(event.dataTransfer, UmbSorterController.activeItem as T);
|
||||
}
|
||||
|
||||
if (this.#config.onStart) {
|
||||
this.#config.onStart({ item: this.#currentItem, element: this.#currentElement! });
|
||||
this.#config.onStart({
|
||||
item: UmbSorterController.activeItem,
|
||||
element: UmbSorterController.activeElement! as ElementType,
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('dragover', this.#handleDragMove);
|
||||
window.addEventListener('dragend', this.#handleDragEnd);
|
||||
// Assuming we can only drag one thing at the time.
|
||||
UmbSorterController.activeSorter = this as unknown as UmbSorterController<unknown>;
|
||||
UmbSorterController.dropSorter = this as unknown as UmbSorterController<unknown>;
|
||||
|
||||
// We must wait one frame before changing the look of the block.
|
||||
this.#rqaId = requestAnimationFrame(() => {
|
||||
// It should be okay to use the same rqaId, as the move does not, or is okay not, to happen on first frame/drag-move.
|
||||
this.#rqaId = undefined;
|
||||
if (this.#currentElement) {
|
||||
this.#currentElement.style.transform = '';
|
||||
this.#setupPlaceholderStyle();
|
||||
if (UmbSorterController.activeElement) {
|
||||
UmbSorterController.activeElement.style.transform = '';
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
#handleDragEnd = async () => {
|
||||
window.removeEventListener('dragover', this.#handleDragMove);
|
||||
window.removeEventListener('dragend', this.#handleDragEnd);
|
||||
#handleDragEnd = async (event?: DragEvent) => {
|
||||
// If not good drop, revert model?
|
||||
|
||||
if (!this.#currentElement || !this.#currentItem) {
|
||||
if (!UmbSorterController.activeElement || !UmbSorterController.activeItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#currentElement.style.transform = '';
|
||||
if (UmbSorterController.originalSorter && event?.dataTransfer != null && event.dataTransfer.dropEffect === 'none') {
|
||||
// Revert move, to start position.
|
||||
UmbSorterController.originalSorter.moveItemInModel(
|
||||
UmbSorterController.originalIndex ?? 0,
|
||||
UmbSorterController.activeSorter!,
|
||||
);
|
||||
}
|
||||
|
||||
UmbSorterController.activeElement.style.transform = '';
|
||||
this.#removePlaceholderStyle();
|
||||
|
||||
this.#stopAutoScroll();
|
||||
this.removeAllowIndication();
|
||||
|
||||
if (this.#config.onEnd) {
|
||||
this.#config.onEnd({ item: this.#currentItem, element: this.#currentElement });
|
||||
this.#config.onEnd({
|
||||
item: UmbSorterController.activeItem,
|
||||
element: UmbSorterController.activeElement as ElementType,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.#rqaId) {
|
||||
@@ -363,19 +435,19 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#rqaId = undefined;
|
||||
}
|
||||
|
||||
this.#currentContainerElement = this.#containerElement;
|
||||
this.#currentContainerCtrl = this;
|
||||
|
||||
this.#currentItem = undefined;
|
||||
this.#currentElement = undefined;
|
||||
this.#currentDragElement = undefined;
|
||||
this.#currentDragRect = undefined;
|
||||
UmbSorterController.activeItem = undefined;
|
||||
UmbSorterController.activeElement = undefined;
|
||||
UmbSorterController.activeDragElement = undefined;
|
||||
UmbSorterController.activeSorter = undefined;
|
||||
UmbSorterController.dropSorter = undefined;
|
||||
UmbSorterController.originalIndex = undefined;
|
||||
UmbSorterController.originalSorter = undefined;
|
||||
this.#dragX = 0;
|
||||
this.#dragY = 0;
|
||||
};
|
||||
|
||||
#handleDragMove = (event: DragEvent) => {
|
||||
if (!this.#currentElement) {
|
||||
#handleDragMove(event: DragEvent) {
|
||||
if (!UmbSorterController.activeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -394,77 +466,37 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
this.handleAutoScroll(this.#dragX, this.#dragY);
|
||||
|
||||
this.#currentDragRect = this.#currentDragElement!.getBoundingClientRect();
|
||||
const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, this.#currentDragRect);
|
||||
const activeDragRect = UmbSorterController.activeDragElement!.getBoundingClientRect();
|
||||
const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, activeDragRect);
|
||||
if (!insideCurrentRect) {
|
||||
if (this.#rqaId === undefined) {
|
||||
this.#rqaId = requestAnimationFrame(this.#updateDragMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#updateDragMove = () => {
|
||||
this.#rqaId = undefined;
|
||||
if (!this.#currentElement || !this.#currentContainerElement || !this.#currentItem) {
|
||||
if (!UmbSorterController.activeElement || !UmbSorterController.activeItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentElementRect = this.#currentElement.getBoundingClientRect();
|
||||
// Maybe no need to check this twice, like we do it before the RAF an inside it, I think its fine to choose one of them.
|
||||
const currentElementRect = UmbSorterController.activeElement.getBoundingClientRect();
|
||||
const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, currentElementRect);
|
||||
if (insideCurrentRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
let toBeCurrentContainerCtrl: UmbSorterController<T, ElementType> | undefined = undefined;
|
||||
|
||||
// If we have a boundarySelector, try it. If we didn't get anything fall back to currentContainerElement:
|
||||
const currentBoundaryElement =
|
||||
(this.#config.boundarySelector
|
||||
? this.#currentContainerElement.closest(this.#config.boundarySelector)
|
||||
: this.#currentContainerElement) ?? this.#currentContainerElement;
|
||||
|
||||
const currentBoundaryRect = currentBoundaryElement.getBoundingClientRect();
|
||||
|
||||
const currentContainerHasItems = this.#currentContainerCtrl.hasOtherItemsThan(this.#currentItem);
|
||||
|
||||
// if empty we will be move likely to accept an item (add 20px to the bounding box)
|
||||
// If we have items we must be 10px within the container to accept the move.
|
||||
const offsetEdge = currentContainerHasItems ? -10 : 20;
|
||||
if (!isWithinRect(this.#dragX, this.#dragY, currentBoundaryRect, offsetEdge)) {
|
||||
// we are outside the current container boundary, so lets see if there is a parent we can move to.
|
||||
|
||||
const parentNode = this.#currentContainerElement.parentNode;
|
||||
if (parentNode && this.#config.containerSelector) {
|
||||
// TODO: support multiple parent shadowDOMs?
|
||||
const parentContainer = (parentNode as ShadowRoot).host
|
||||
? (parentNode as ShadowRoot).host.closest(this.#config.containerSelector)
|
||||
: (parentNode as HTMLElement).closest(this.#config.containerSelector);
|
||||
if (parentContainer) {
|
||||
const parentContainerCtrl = (parentContainer as any)['__umbBlockGridSorterController']();
|
||||
if (parentContainerCtrl.unique === this.controllerAlias) {
|
||||
this.#currentContainerElement = parentContainer as Element;
|
||||
toBeCurrentContainerCtrl = parentContainerCtrl;
|
||||
if (this.#config.onContainerChange) {
|
||||
this.#config.onContainerChange({
|
||||
item: this.#currentItem,
|
||||
element: this.#currentElement,
|
||||
//ownerVM: this.#currentContainerVM.ownerVM,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const containerElement = this.#useContainerShadowRoot
|
||||
? this.#currentContainerElement.shadowRoot ?? this.#currentContainerElement
|
||||
: this.#currentContainerElement;
|
||||
? this.#containerElement.shadowRoot ?? this.#containerElement
|
||||
: this.#containerElement;
|
||||
|
||||
// We want to retrieve the children of the container, every time to ensure we got the right order and index
|
||||
const orderedContainerElements = Array.from(containerElement.querySelectorAll(this.#config.itemSelector));
|
||||
|
||||
const currentContainerRect = this.#currentContainerElement.getBoundingClientRect();
|
||||
const currentContainerRect = this.#containerElement.getBoundingClientRect();
|
||||
|
||||
// gather elements on the same row.
|
||||
const elementsInSameRow = [];
|
||||
@@ -476,7 +508,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
const dragElement = this.#config.draggableSelector ? el.querySelector(this.#config.draggableSelector) : el;
|
||||
if (dragElement) {
|
||||
const dragElementRect = dragElement.getBoundingClientRect();
|
||||
if (el !== this.#currentElement) {
|
||||
if (el !== UmbSorterController.activeElement) {
|
||||
elementsInSameRow.push({ el: el, dragRect: dragElementRect });
|
||||
} else {
|
||||
placeholderIsInThisRow = true;
|
||||
@@ -502,65 +534,21 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
if (foundEl) {
|
||||
// If we are on top or closest to our self, we should not do anything.
|
||||
if (foundEl === this.#currentElement) {
|
||||
if (foundEl === UmbSorterController.activeElement) {
|
||||
return;
|
||||
}
|
||||
const isInsideFound = isWithinRect(this.#dragX, this.#dragY, foundElDragRect, 0);
|
||||
|
||||
// If we are inside the found element, lets look for sub containers.
|
||||
// use the itemHasNestedContainersResolver, if not configured fallback to looking for the existence of a container via DOM.
|
||||
// TODO: Ability to look into shadowDOMs for sub containers?
|
||||
if (
|
||||
isInsideFound && this.#config.itemHasNestedContainersResolver
|
||||
? this.#config.itemHasNestedContainersResolver(foundEl)
|
||||
: (foundEl as HTMLElement).querySelector(this.#config.containerSelector)
|
||||
) {
|
||||
// Find all sub containers:
|
||||
const subLayouts = (foundEl as HTMLElement).querySelectorAll(this.#config.containerSelector);
|
||||
for (const subLayoutEl of subLayouts) {
|
||||
// Use boundary element or fallback to container element.
|
||||
const subBoundaryElement =
|
||||
(this.#config.boundarySelector ? subLayoutEl.closest(this.#config.boundarySelector) : subLayoutEl) ||
|
||||
subLayoutEl;
|
||||
const subBoundaryRect = subBoundaryElement.getBoundingClientRect();
|
||||
|
||||
const subContainerHasItems = subLayoutEl.querySelector(
|
||||
this.#config.itemSelector + ':not(.' + this.#config.placeholderClass + ')',
|
||||
);
|
||||
// gather elements on the same row.
|
||||
const subOffsetEdge = subContainerHasItems ? -10 : 20;
|
||||
if (isWithinRect(this.#dragX, this.#dragY, subBoundaryRect, subOffsetEdge)) {
|
||||
const subCtrl = (subLayoutEl as any)['__umbBlockGridSorterController']();
|
||||
if (subCtrl.unique === this.controllerAlias) {
|
||||
this.#currentContainerElement = subLayoutEl as HTMLElement;
|
||||
toBeCurrentContainerCtrl = subCtrl;
|
||||
if (this.#config.onContainerChange) {
|
||||
this.#config.onContainerChange({
|
||||
item: this.#currentItem,
|
||||
element: this.#currentElement,
|
||||
//ownerVM: this.#currentContainerVM.ownerVM,
|
||||
});
|
||||
}
|
||||
this.#updateDragMove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Indication if drop is good:
|
||||
if (
|
||||
this.updateAllowIndication(toBeCurrentContainerCtrl ?? this.#currentContainerCtrl, this.#currentItem) === false
|
||||
) {
|
||||
if (this.updateAllowIndication(UmbSorterController.activeItem) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const verticalDirection = this.#config.resolveVerticalDirection
|
||||
? this.#config.resolveVerticalDirection({
|
||||
containerElement: this.#currentContainerElement,
|
||||
containerElement: this.#containerElement,
|
||||
containerRect: currentContainerRect,
|
||||
item: this.#currentItem,
|
||||
element: this.#currentElement,
|
||||
item: UmbSorterController.activeItem,
|
||||
element: UmbSorterController.activeElement as ElementType,
|
||||
elementRect: currentElementRect,
|
||||
relatedElement: foundEl,
|
||||
relatedRect: foundElDragRect,
|
||||
@@ -599,49 +587,55 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
const foundElIndex = orderedContainerElements.indexOf(foundEl);
|
||||
const newIndex = placeAfter ? foundElIndex + 1 : foundElIndex;
|
||||
this.#moveElementTo(toBeCurrentContainerCtrl, newIndex);
|
||||
this.#moveElementTo(newIndex);
|
||||
|
||||
return;
|
||||
}
|
||||
// We skipped the above part cause we are above or below container:
|
||||
// We skipped the above part cause we are above or below container, or within an empty container:
|
||||
|
||||
// Indication if drop is good:
|
||||
if (
|
||||
this.updateAllowIndication(toBeCurrentContainerCtrl ?? this.#currentContainerCtrl, this.#currentItem) === false
|
||||
) {
|
||||
if (this.updateAllowIndication(UmbSorterController.activeItem) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#dragY < currentContainerRect.top) {
|
||||
this.#moveElementTo(toBeCurrentContainerCtrl, 0);
|
||||
if (this.#model.length === 0) {
|
||||
// Here is no items, so we should just move into the top of the container.
|
||||
this.#moveElementTo(0);
|
||||
} else if (this.#dragY < currentContainerRect.top) {
|
||||
this.#moveElementTo(0);
|
||||
} else if (this.#dragY > currentContainerRect.bottom) {
|
||||
this.#moveElementTo(toBeCurrentContainerCtrl, -1);
|
||||
this.#moveElementTo(-1);
|
||||
}
|
||||
};
|
||||
|
||||
async #moveElementTo(containerCtrl: UmbSorterController<T, ElementType> | undefined, newIndex: number) {
|
||||
if (!this.#currentElement) {
|
||||
//
|
||||
async #moveElementTo(newIndex: number) {
|
||||
if (!UmbSorterController.activeElement || !UmbSorterController.activeSorter) {
|
||||
return;
|
||||
}
|
||||
|
||||
containerCtrl ??= this as UmbSorterController<T, ElementType>;
|
||||
const requestingSorter = UmbSorterController.dropSorter;
|
||||
if (!requestingSorter) {
|
||||
throw new Error('Could not find requestingSorter');
|
||||
}
|
||||
|
||||
// If same container and same index, do nothing:
|
||||
if (this.#currentContainerCtrl === containerCtrl && this.#currentIndex === newIndex) return;
|
||||
if (requestingSorter === UmbSorterController.activeSorter && UmbSorterController.activeIndex === newIndex) return;
|
||||
|
||||
if (await containerCtrl.moveItemInModel(newIndex, this.#currentElement, this.#currentContainerCtrl)) {
|
||||
this.#currentContainerCtrl = containerCtrl;
|
||||
this.#currentIndex = newIndex;
|
||||
}
|
||||
await requestingSorter.moveItemInModel(newIndex, UmbSorterController.activeSorter);
|
||||
}
|
||||
|
||||
/** Management methods: */
|
||||
|
||||
public getItemOfElement(element: ElementType) {
|
||||
if (!element) {
|
||||
return undefined;
|
||||
throw new Error('Element was not defined');
|
||||
}
|
||||
return this.#model.find((entry: T) => this.#config.compareElementToModel(element, entry));
|
||||
const elementUnique = this.#config.getUniqueOfElement(element);
|
||||
if (!elementUnique) {
|
||||
throw new Error('Could not find unique of element');
|
||||
}
|
||||
return this.#model.find((entry: T) => elementUnique === this.#config.getUniqueOfModel(entry));
|
||||
}
|
||||
|
||||
public async removeItem(item: T) {
|
||||
@@ -663,13 +657,34 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
public async insertItem(item: T, newIndex: number = 0) {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.#config.performItemInsert) {
|
||||
const result = await this.#config.performItemInsert({ item, newIndex });
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const newModel = [...this.#model];
|
||||
newModel.splice(newIndex, 0, item);
|
||||
this.#model = newModel;
|
||||
this.#config.onChange?.({ model: newModel, item });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
public hasOtherItemsThan(item: T) {
|
||||
return this.#model.filter((x) => x !== item).length > 0;
|
||||
}
|
||||
|
||||
public async moveItemInModel(newIndex: number, element: ElementType, fromCtrl: UmbSorterController<T, ElementType>) {
|
||||
const item = fromCtrl.getItemOfElement(element);
|
||||
// TODO: Could get item via attr.
|
||||
public async moveItemInModel(newIndex: number, fromCtrl: UmbSorterController<unknown>) {
|
||||
const item = UmbSorterController.activeItem;
|
||||
if (!item) {
|
||||
console.error('Could not find item of sync item');
|
||||
return false;
|
||||
@@ -678,13 +693,17 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
return false;
|
||||
}
|
||||
|
||||
const localMove = fromCtrl === this;
|
||||
const localMove = fromCtrl === (this as any);
|
||||
|
||||
if (localMove) {
|
||||
// Local move:
|
||||
|
||||
// TODO: Maybe this should be replaceable/configurable:
|
||||
const oldIndex = this.#model.indexOf(item);
|
||||
if (oldIndex === -1) {
|
||||
console.error('Could not find item in model');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.#config.performItemMove) {
|
||||
const result = await this.#config.performItemMove({ item, newIndex, oldIndex });
|
||||
@@ -701,6 +720,8 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#model = newModel;
|
||||
this.#config.onChange?.({ model: newModel, item });
|
||||
}
|
||||
|
||||
UmbSorterController.activeIndex = newIndex;
|
||||
} else {
|
||||
// Not a local move:
|
||||
|
||||
@@ -720,12 +741,18 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#model = newModel;
|
||||
this.#config.onChange?.({ model: newModel, item });
|
||||
}
|
||||
|
||||
// If everything went well, we can set new activeSorter to this:
|
||||
UmbSorterController.activeSorter = this as unknown as UmbSorterController<unknown>;
|
||||
UmbSorterController.activeIndex = newIndex;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
updateAllowIndication(controller: UmbSorterController<T, ElementType>, item: T) {
|
||||
updateAllowIndication(item: T) {
|
||||
// TODO: Allow indication.
|
||||
/*
|
||||
// Remove old indication:
|
||||
if (this.#lastIndicationContainerCtrl !== null && this.#lastIndicationContainerCtrl !== controller) {
|
||||
this.#lastIndicationContainerCtrl.notifyAllowed();
|
||||
@@ -739,6 +766,8 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
controller.notifyDisallowed(); // This block is not accepted to we will indicate that its not allowed.
|
||||
return false;
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
removeAllowIndication() {
|
||||
// Remove old indication:
|
||||
@@ -827,7 +856,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
destroy() {
|
||||
// Do something when host element is destroyed.
|
||||
if (this.#currentElement) {
|
||||
if (UmbSorterController.activeElement) {
|
||||
this.#handleDragEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/addon-docs';
|
||||
|
||||
import * as LocalizeStories from './sorter.stories';
|
||||
|
||||
<Meta title="API/Drag and Drop/Intro" />
|
||||
|
||||
# Drag and Drop
|
||||
|
||||
Drag and Drop can be done by using the `UmbSorterController`
|
||||
|
||||
To get started using drag and drop, finish the following steps:
|
||||
|
||||
- Preparing the model
|
||||
- Setting the configuration
|
||||
- Registering the controller
|
||||
|
||||
#### Preparing the model
|
||||
|
||||
The SorterController needs a model to know what item it is we are dealing with.
|
||||
|
||||
```typescript
|
||||
type MySortEntryType = {
|
||||
id: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const awesomeModel: Array<MySortEntryType> = [
|
||||
{
|
||||
id: '0',
|
||||
value: 'Entry 0',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
value: 'Entry 1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'Entry 2',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
#### Setting the configuration
|
||||
|
||||
When you know the model of which that is being sorted, you can set up the configuration.
|
||||
The configuration has a lot of optional options, but the required ones are:
|
||||
|
||||
- compareElementToModel()
|
||||
- querySelectModelToElement()
|
||||
- identifier
|
||||
- itemSelector
|
||||
- containerSelector
|
||||
|
||||
It can be set up as follows:
|
||||
|
||||
```typescript
|
||||
import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
type MySortEntryType = {...};
|
||||
const awesomeModel: Array<MySortEntryType> = [...];
|
||||
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {
|
||||
compareElementToModel: (element: HTMLElement, model: MySortEntryType) => {
|
||||
return element.getAttribute('data-sort-entry-id') === model.id;
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: MySortEntryType) => {
|
||||
return container.querySelector('[data-sort-entry-id=' + modelEntry.id + ']');
|
||||
},
|
||||
identifier: 'test-sorter',
|
||||
itemSelector: 'li',
|
||||
containerSelector: 'ul',
|
||||
};
|
||||
|
||||
export class MyElement extends UmbElementMixin(LitElement) {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
${awesomeModel.map(
|
||||
(entry) =>
|
||||
html`<li data-sort-entry-id="${entry.id}">
|
||||
<span>${entry.value}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Registering the controller
|
||||
|
||||
When the model and configuration are available we can register the controller and tell the controller what model we are using.
|
||||
|
||||
```typescript
|
||||
import { UmbSorterController, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
type MySortEntryType = {...}
|
||||
const awesomeModel: Array<MySortEntryType> = [...]
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {...}
|
||||
|
||||
|
||||
export class MyElement extends UmbElementMixin(LitElement) {
|
||||
#sorter = new UmbSorterController(this, {...MY_SORTER_CONFIG,
|
||||
onChange: ({ model }) => {
|
||||
const oldValue = this.awesomeModel;
|
||||
this.awesomeModel = model;
|
||||
this.requestUpdate('awesomeModel', oldValue);
|
||||
},
|
||||
});
|
||||
|
||||
constructor() {
|
||||
this.#sorter.setModel(awesomeModel);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ul>
|
||||
${awesomeModel.map(
|
||||
(entry) =>
|
||||
html`<li data-sort-entry-id="${entry.id}">
|
||||
<span>${entry.value}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Placeholder
|
||||
|
||||
While dragging an entry, the entry will get an additional class that can be styled.
|
||||
The class is by default `--umb-sorter-placeholder` but can be changed via the configuration to a different value.
|
||||
|
||||
```typescript
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {
|
||||
...
|
||||
placeholderClass: 'dragging-now',
|
||||
};
|
||||
```
|
||||
|
||||
```typescript
|
||||
static styles = [
|
||||
css`
|
||||
li {
|
||||
display:relative;
|
||||
}
|
||||
|
||||
li.dragging-now span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
li.dragging-now::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border: 1px dashed grey;
|
||||
}
|
||||
`,
|
||||
];
|
||||
```
|
||||
|
||||
### Horizontal sorting
|
||||
|
||||
By default, the sorter controller will sort vertically. You can sort your model horizontally by setting the `resolveVerticalDirection` to return false.
|
||||
|
||||
```typescript
|
||||
const MY_SORTER_CONFIG: UmbSorterConfig<MySortEntryType> = {
|
||||
...
|
||||
resolveVerticalDirection: () => return false,
|
||||
};
|
||||
```
|
||||
|
||||
### Performing logic when using the controller (TODO: Better title)
|
||||
|
||||
Let's say your model has a property sortOrder that you would like to update when the entry is being sorted.
|
||||
You can add your code logic in the configuration option `performItemInsert` and `performItemRemove`
|
||||
|
||||
```typescript
|
||||
export class MyElement extends UmbElementMixin(LitElement) {
|
||||
#sorter = new UmbSorterController(this, {
|
||||
...SORTER_CONFIG,
|
||||
performItemInsert: ({ item, newIndex }) => {
|
||||
// Insert logic that updates the model, so the item gets the new index in the model.
|
||||
return true;
|
||||
},
|
||||
performItemRemove: () => {
|
||||
// Insert logic that updates the model, so the item gets removed from the model.
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/web-components';
|
||||
import type UmbTestSorterControllerElement from './test-sorter-controller.element.js';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import './test-sorter-controller.element.js';
|
||||
|
||||
const meta: Meta<UmbTestSorterControllerElement> = {
|
||||
title: 'API/Drag and Drop/Sorter',
|
||||
component: 'test-my-sorter-controller',
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return html`<div
|
||||
style="margin:2rem auto; width: 50%; min-height: 350px; padding: 20px; box-sizing: border-box; background:white;">
|
||||
<p>
|
||||
<strong>Drag and drop the items to sort them.</strong>
|
||||
</p>
|
||||
${Story()}
|
||||
</div>`;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<UmbTestSorterControllerElement>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@@ -1,131 +0,0 @@
|
||||
import type { UmbSorterConfig} from '../sorter.controller.js';
|
||||
import { UmbSorterController } from '../sorter.controller.js';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
type SortEntryType = {
|
||||
id: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<SortEntryType> = {
|
||||
compareElementToModel: (element: HTMLElement, model: SortEntryType) => {
|
||||
return element.getAttribute('data-sort-entry-id') === model.id;
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: SortEntryType) => {
|
||||
return container.querySelector('[data-sort-entry-id=' + modelEntry.id + ']');
|
||||
},
|
||||
identifier: 'test-sorter',
|
||||
itemSelector: 'li',
|
||||
containerSelector: 'ul',
|
||||
};
|
||||
const model: Array<SortEntryType> = [
|
||||
{
|
||||
id: '0',
|
||||
value: 'Entry 0',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
value: 'Entry 1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
value: 'Entry 2',
|
||||
},
|
||||
];
|
||||
|
||||
@customElement('test-my-sorter-controller')
|
||||
export default class UmbTestSorterControllerElement extends UmbLitElement {
|
||||
public sorter;
|
||||
|
||||
@state()
|
||||
private vertical = true;
|
||||
|
||||
@state()
|
||||
private _items: Array<SortEntryType> = [...model];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.sorter = new UmbSorterController(this, {
|
||||
...SORTER_CONFIG,
|
||||
resolveVerticalDirection: () => {
|
||||
this.vertical ? true : false;
|
||||
},
|
||||
onChange: ({ model }) => {
|
||||
const oldValue = this._items;
|
||||
this._items = model;
|
||||
this.requestUpdate('_items', oldValue);
|
||||
},
|
||||
});
|
||||
this.sorter.setModel(model);
|
||||
}
|
||||
|
||||
#toggle() {
|
||||
this.vertical = !this.vertical;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button label="Change direction" look="outline" color="positive" @click=${this.#toggle}>
|
||||
Horizontal/Vertical
|
||||
</uui-button>
|
||||
<ul class="${this.vertical ? 'vertical' : 'horizontal'}">
|
||||
${this._items.map(
|
||||
(entry) =>
|
||||
html`<li class="item" data-sort-entry-id="${entry.id}">
|
||||
<span><uui-icon name="icon-wand"></uui-icon>${entry.value}</span>
|
||||
</li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
ul.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: grab;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
border-radius: var(--uui-border-radius);
|
||||
}
|
||||
|
||||
li span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
li.--umb-sorter-placeholder span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
li.--umb-sorter-placeholder::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border-radius: var(--uui-border-radius);
|
||||
border: 1px dashed var(--uui-color-divider-emphasis);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import type {
|
||||
DocumentTypePropertyTypeContainerResponseModel,
|
||||
PropertyTypeContainerModelBaseModel,
|
||||
} from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
const SORTER_CONFIG_HORIZONTAL: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: DocumentTypePropertyTypeContainerResponseModel) => {
|
||||
return element.getAttribute('data-umb-tabs-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-tabs-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => {
|
||||
return container.querySelector(`[data-umb-tabs-id='` + modelEntry.id + `']`);
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-tabs-sorter',
|
||||
itemSelector: '[data-umb-tabs-id]',
|
||||
@@ -21,11 +18,11 @@ const SORTER_CONFIG_HORIZONTAL: UmbSorterConfig<PropertyTypeContainerModelBaseMo
|
||||
};
|
||||
|
||||
const SORTER_CONFIG_VERTICAL: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: DocumentTypePropertyTypeContainerResponseModel) => {
|
||||
return element.getAttribute('data-umb-property-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-property-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => {
|
||||
return container.querySelector(`[data-umb-property-id='` + modelEntry.id + `']`);
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-property-sorter',
|
||||
itemSelector: '[data-umb-property-id]',
|
||||
|
||||
@@ -12,11 +12,11 @@ import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UMB_PROPERTY_SETTINGS_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbPropertyTypeModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: UmbPropertyTypeModel) => {
|
||||
return element.getAttribute('data-umb-property-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-property-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: UmbPropertyTypeModel) => {
|
||||
return container.querySelector('[data-umb-property-id=' + modelEntry.id + ']');
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-property-sorter',
|
||||
itemSelector: '[data-umb-property-id]',
|
||||
|
||||
@@ -12,11 +12,11 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import './document-type-workspace-view-edit-properties.element.js';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: PropertyTypeContainerModelBaseModel) => {
|
||||
return element.getAttribute('data-umb-group-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-group-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => {
|
||||
return container.querySelector('data-umb-group-id=[' + modelEntry.id + ']');
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-group-sorter',
|
||||
itemSelector: '[data-umb-group-id]',
|
||||
|
||||
@@ -6,10 +6,7 @@ import type { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/ext
|
||||
import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
|
||||
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type {
|
||||
DocumentTypePropertyTypeContainerResponseModel,
|
||||
PropertyTypeContainerModelBaseModel,
|
||||
} from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -20,11 +17,11 @@ import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: DocumentTypePropertyTypeContainerResponseModel) => {
|
||||
return element.getAttribute('data-umb-tabs-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-tabs-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => {
|
||||
return container.querySelector(`[data-umb-tabs-id='` + modelEntry.id + `']`);
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-tabs-sorter',
|
||||
itemSelector: '[data-umb-tabs-id]',
|
||||
|
||||
@@ -9,10 +9,12 @@ import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffi
|
||||
import type { UmbDocumentItemModel } from '@umbraco-cms/backoffice/document';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<string> = {
|
||||
compareElementToModel: (element, model) => {
|
||||
return element.getAttribute('detail') === model;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('detail');
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry;
|
||||
},
|
||||
querySelectModelToElement: () => null,
|
||||
identifier: 'Umb.SorterIdentifier.InputDocument',
|
||||
itemSelector: 'uui-ref-node',
|
||||
containerSelector: 'uui-ref-list',
|
||||
|
||||
@@ -12,11 +12,11 @@ import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UMB_PROPERTY_SETTINGS_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<UmbPropertyTypeModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: UmbPropertyTypeModel) => {
|
||||
return element.getAttribute('data-umb-property-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-tabs-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: UmbPropertyTypeModel) => {
|
||||
return container.querySelector('[data-umb-property-id=' + modelEntry.id + ']');
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-property-sorter',
|
||||
itemSelector: '[data-umb-property-id]',
|
||||
|
||||
@@ -12,11 +12,11 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import './media-type-workspace-view-edit-properties.element.js';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: PropertyTypeContainerModelBaseModel) => {
|
||||
return element.getAttribute('data-umb-group-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-group-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => {
|
||||
return container.querySelector('data-umb-group-id=[' + modelEntry.id + ']');
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-group-sorter',
|
||||
itemSelector: '[data-umb-group-id]',
|
||||
|
||||
@@ -6,10 +6,7 @@ import type { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/ext
|
||||
import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
|
||||
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type {
|
||||
MediaTypePropertyTypeContainerResponseModel,
|
||||
PropertyTypeContainerModelBaseModel,
|
||||
} from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -20,11 +17,11 @@ import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
compareElementToModel: (element: HTMLElement, model: MediaTypePropertyTypeContainerResponseModel) => {
|
||||
return element.getAttribute('data-umb-tabs-id') === model.id;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('data-umb-tabs-id');
|
||||
},
|
||||
querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => {
|
||||
return container.querySelector(`[data-umb-tabs-id='` + modelEntry.id + `']`);
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.id;
|
||||
},
|
||||
identifier: 'content-type-tabs-sorter',
|
||||
itemSelector: '[data-umb-tabs-id]',
|
||||
|
||||
@@ -8,10 +8,12 @@ import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbra
|
||||
import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<string> = {
|
||||
compareElementToModel: (element, model) => {
|
||||
return element.getAttribute('detail') === model;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('detail');
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry;
|
||||
},
|
||||
querySelectModelToElement: () => null,
|
||||
identifier: 'Umb.SorterIdentifier.InputMedia',
|
||||
itemSelector: 'uui-card-media',
|
||||
containerSelector: '.container',
|
||||
|
||||
@@ -7,10 +7,12 @@ import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbra
|
||||
import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<string> = {
|
||||
compareElementToModel: (element, model) => {
|
||||
return element.getAttribute('detail') === model;
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.getAttribute('detail');
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry;
|
||||
},
|
||||
querySelectModelToElement: () => null,
|
||||
identifier: 'Umb.SorterIdentifier.InputMember',
|
||||
itemSelector: 'uui-ref-node',
|
||||
containerSelector: 'uui-ref-list',
|
||||
|
||||
Reference in New Issue
Block a user