diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/README.md b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/README.md new file mode 100644 index 0000000000..a5f65cf26b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/README.md @@ -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. diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/index.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/index.ts new file mode 100644 index 0000000000..0771859fe9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/index.ts @@ -0,0 +1,15 @@ +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'dashboard', + name: 'Example Sorter Dashboard', + alias: 'example.dashboard.dataset', + element: () => import('./sorter-dashboard.js'), + weight: 900, + meta: { + label: 'Sorter example', + pathname: 'sorter-example', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-dashboard.ts new file mode 100644 index 0000000000..36f96e5de2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-dashboard.ts @@ -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` + +
+
Notice this example still only support single group of Sorter.
+ + +
+
+ `; + } + + 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; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-group.ts new file mode 100644 index 0000000000..85688f727d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-group.ts @@ -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(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` +
+ ${repeat( + this.items, + (item) => item.name, + (item) => + html` + + ${item.children ? html`` : ''} + `, + )} +
+ `; + } + + 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; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-item.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-item.ts new file mode 100644 index 0000000000..a3f7ef5fb3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-nested-containers/sorter-item.ts @@ -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` +
+ ${this.name} + + +
+ + `; + } + + 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; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md index 3dc57f3788..b92b05220d 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md @@ -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. diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index 0e88318ab3..7747317257 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -40,7 +40,6 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { return html`
-
Notice this example still only support single group of Sorter.
diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 4ce3125463..6975854446 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -10,73 +10,42 @@ export type ModelEntryType = { name: string; }; -const SORTER_CONFIG: UmbSorterConfig = { - 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(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` - + `, )} @@ -105,6 +74,10 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { .sorter-placeholder { opacity: 0.2; } + + .sorter-container { + min-height: 20px; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts index 495370bb00..3dd7f0b5e7 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts @@ -13,8 +13,11 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) { render() { return html` - ${this.name} - +
+ ${this.name} + + +
`; } @@ -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: + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 2bcc827d31..2379afa77c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -21,11 +21,11 @@ export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {} export interface UmbBlockListValueModel extends UmbBlockValueType {} const SORTER_CONFIG: UmbSorterConfig = { - 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', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-notification-container/backoffice-notification-container.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-notification-container/backoffice-notification-container.element.ts index 1dd3a1cced..07bbf076ea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-notification-container/backoffice-notification-container.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/backoffice-notification-container/backoffice-notification-container.element.ts @@ -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] }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts index 4ca54fa5fd..95f27e1e55 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts @@ -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 = { - compareElementToModel: (element: HTMLElement, model: UmbSwatchDetails) => { - return element.getAttribute('data-sort-entry-id') === model.value; +const SORTER_CONFIG: UmbSorterConfig = { + 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` this.#onChange(event, index)} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts index 052188d85c..7945201ca9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts @@ -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; @@ -14,11 +14,11 @@ export interface MultipleTextStringValueItem { } const SORTER_CONFIG: UmbSorterConfig = { - 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', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index b0e85d090b..096b9fc2e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -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 = { - 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 = { // External type with some properties optional, as they have defaults: export type UmbSorterConfig = Omit< INTERNAL_UmbSorterConfig, - 'ignorerSelector' | 'containerSelector' + 'ignorerSelector' | 'containerSelector' | 'identifier' > & - Partial, 'ignorerSelector' | 'containerSelector'>>; + Partial, 'ignorerSelector' | 'containerSelector' | 'identifier'>>; /** * @export @@ -112,6 +108,23 @@ export type UmbSorterConfig = * @description This controller can make user able to sort items. */ export class UmbSorterController implements UmbController { + // + // A sorter that is requested to become the next sorter: + static originalSorter?: UmbSorterController; + static originalIndex?: number; + + // A sorter that is requested to become the next sorter: + static dropSorter?: UmbSorterController; + + // The sorter of which the element is located within: + static activeSorter?: UmbSorterController; + + // Information about the current dragged item/element: + static activeIndex?: number; + static activeItem?: any; + static activeElement?: HTMLElement; + static activeDragElement?: Element; + #host; #config: INTERNAL_UmbSorterConfig; #observer; @@ -120,24 +133,20 @@ export class UmbSorterController = 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 | 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 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 { - return this; - }; + // TODO: Do we need to handle dragleave? this.#observer.disconnect(); @@ -215,15 +227,50 @@ export class UmbSorterController { + //if(UmbSorterController.activeSorter === this) return; + const dropSorter = UmbSorterController.dropSorter as unknown as UmbSorterController; + 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; + + // 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; + 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; + UmbSorterController.dropSorter = this as unknown as UmbSorterController; // 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 { - if (!this.#currentElement) { + #handleDragMove(event: DragEvent) { + if (!UmbSorterController.activeElement) { return; } @@ -394,77 +466,37 @@ export class UmbSorterController { 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 | 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 currentContainerRect.bottom) { - this.#moveElementTo(toBeCurrentContainerCtrl, -1); + this.#moveElementTo(-1); } }; - async #moveElementTo(containerCtrl: UmbSorterController | undefined, newIndex: number) { - if (!this.#currentElement) { + // + async #moveElementTo(newIndex: number) { + if (!UmbSorterController.activeElement || !UmbSorterController.activeSorter) { return; } - containerCtrl ??= this as UmbSorterController; + 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 x !== item).length > 0; } - public async moveItemInModel(newIndex: number, element: ElementType, fromCtrl: UmbSorterController) { - const item = fromCtrl.getItemOfElement(element); + // TODO: Could get item via attr. + public async moveItemInModel(newIndex: number, fromCtrl: UmbSorterController) { + const item = UmbSorterController.activeItem; if (!item) { console.error('Could not find item of sync item'); return false; @@ -678,13 +693,17 @@ export class UmbSorterController; + UmbSorterController.activeIndex = newIndex; } return true; } - updateAllowIndication(controller: UmbSorterController, 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 - -# 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 = [ - { - 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 = [...]; - -const MY_SORTER_CONFIG: UmbSorterConfig = { - 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` -
    - ${awesomeModel.map( - (entry) => - html`
  • - ${entry.value} -
  • `, - )} -
- `; - } -} -``` - -#### 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 = [...] -const MY_SORTER_CONFIG: UmbSorterConfig = {...} - - -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` -
    - ${awesomeModel.map( - (entry) => - html`
  • - ${entry.value} -
  • `, - )} -
- `; - } -} -``` - -### 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 = { - ... - 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 = { - ... - 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; - }, - }); -} -``` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter.stories.ts deleted file mode 100644 index 116ec943e1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter.stories.ts +++ /dev/null @@ -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 = { - title: 'API/Drag and Drop/Sorter', - component: 'test-my-sorter-controller', - decorators: [ - (Story) => { - return html`
-

- Drag and drop the items to sort them. -

- ${Story()} -
`; - }, - ], -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts deleted file mode 100644 index 757b758a47..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts +++ /dev/null @@ -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 = { - 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 = [ - { - 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 = [...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` - - Horizontal/Vertical - -
    - ${this._items.map( - (entry) => - html`
  • - ${entry.value} -
  • `, - )} -
- `; - } - - 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); - } - `, - ]; -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-sorter.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-sorter.ts index ea7ad4bc6e..2f139aec68 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-sorter.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-sorter.ts @@ -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 = { - 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 = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts index 5cdda1e821..b93078c07f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts @@ -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 = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts index 94a2aa5a03..41c56db3ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts @@ -12,11 +12,11 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import './document-type-workspace-view-edit-properties.element.js'; const SORTER_CONFIG: UmbSorterConfig = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts index d2027164fa..a87bc48f51 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts @@ -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 = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 922b76e266..7fcf621d17 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -9,10 +9,12 @@ import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffi import type { UmbDocumentItemModel } from '@umbraco-cms/backoffice/document'; const SORTER_CONFIG: UmbSorterConfig = { - 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', diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts index 81cb48d322..00d6701914 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts @@ -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 = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts index e7aa540062..86fb523d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts @@ -12,11 +12,11 @@ import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import './media-type-workspace-view-edit-properties.element.js'; const SORTER_CONFIG: UmbSorterConfig = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts index 5f023cc1e1..9d891d5964 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts @@ -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 = { - 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]', diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts index 068a5c42fc..121480345d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts @@ -8,10 +8,12 @@ import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbra import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; const SORTER_CONFIG: UmbSorterConfig = { - 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', diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts index cfedcddf0f..f6ec06efb9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/components/input-member/input-member.element.ts @@ -7,10 +7,12 @@ import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbra import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; const SORTER_CONFIG: UmbSorterConfig = { - 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',