diff --git a/src/Umbraco.Web.UI.Client/libs/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/libs/sorter/sorter.controller.ts index 2bafd25926..4da2065bb5 100644 --- a/src/Umbraco.Web.UI.Client/libs/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/libs/sorter/sorter.controller.ts @@ -1,9 +1,10 @@ import { UmbControllerInterface, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { NullValue } from 'rollup'; const autoScrollSensitivity = 50; const autoScrollSpeed = 16; -function isWithinRect(x: number, y: number, rect: DOMRect, modifier: number = 0) { +function isWithinRect(x: number, y: number, rect: DOMRect, modifier = 0) { return x > rect.left - modifier && x < rect.right + modifier && y > rect.top - modifier && y < rect.bottom + modifier; } @@ -61,14 +62,14 @@ function destroyPreventEvent(element: Element) { element.removeAttribute('draggable'); } -export type UmbSorterConfig = { +type INTERNAL_UmbSorterConfig = { compareElementToModel: (el: HTMLElement, modelEntry: T) => boolean; - querySelectModelToElement: (container: HTMLElement, modelEntry: T) => HTMLElement; + querySelectModelToElement: (container: HTMLElement, modelEntry: T) => HTMLElement | null; identifier: string; - ignorerSelector: string; itemSelector: string; - placeholderClass: string; containerSelector: string; + ignorerSelector: string; + placeholderClass: string; draggableSelector?: string; boundarySelector?: string; dataTransferResolver?: (dataTransfer: DataTransfer | null, currentItem: T) => void; @@ -98,6 +99,19 @@ export type UmbSorterConfig = { }) => void; }; +// External type with some properties optional, as they have defaults: +export type UmbSorterConfig = Omit< + INTERNAL_UmbSorterConfig, + 'placeholderClass' | 'ignorerSelector' | 'containerSelector' +> & + Partial, 'placeholderClass' | 'ignorerSelector' | 'containerSelector'>>; + +/** + * @export + * @class UmbSorterController + * @implements {UmbControllerInterface} + * @description This controller can make user able to sort items. + */ export class UmbSorterController implements UmbControllerInterface { #host; #config; @@ -106,8 +120,9 @@ export class UmbSorterController implements UmbControllerInterface { #model: Array = []; #rqaId?: number; + #containerElement!: Element; #currentContainerVM = this; - #currentContainerElement: Element; + #currentContainerElement: Element | null = null; #scrollElement?: Element | null; #currentElement?: HTMLElement; @@ -126,10 +141,15 @@ export class UmbSorterController implements UmbControllerInterface { constructor(host: UmbControllerHostElement, config: UmbSorterConfig) { this.#host = host; - this.#config = config; + + // Set defaults: + config.ignorerSelector ??= 'a, img, iframe'; + config.placeholderClass ??= 'umb-drag-placeholder'; + + this.#config = config as INTERNAL_UmbSorterConfig; host.addController(this); - this.#currentContainerElement = host; + //this.#currentContainerElement = host; this.#observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { @@ -146,33 +166,52 @@ export class UmbSorterController implements UmbControllerInterface { }); }); - (host as any)['__umbBlockGridSorterController'] = () => { - return this; - }; host.addEventListener('dragover', preventDragOver); } setModel(model: Array) { + if (this.#model) { + // TODO: Some updates might need to be done, as the modal is about to changed? Do make the changes after setting the model?.. + } this.#model = model; - // TODO: Some update? } hostConnected() { - this.#observer.observe(this.#host, { childList: true, subtree: false }); + const containerEl = + (this.#config.containerSelector ? this.#host.querySelector(this.#config.containerSelector) : this.#host) ?? + this.#host; + + (containerEl as any)['__umbBlockGridSorterController'] = () => { + return this; + }; + + if (this.#currentContainerElement === this.#containerElement) { + this.#currentContainerElement = containerEl; + } + this.#containerElement = containerEl; + + // TODO: Clean up?? + this.#observer.disconnect(); + this.#observer.observe(this.#containerElement, { childList: true, subtree: false }); } hostDisconnected() { + // TODO: Clean up?? this.#observer.disconnect(); } setupItem(element: HTMLElement) { - setupIgnorerElements(element, this.#config.ignorerSelector); + if (this.#config.ignorerSelector) { + setupIgnorerElements(element, this.#config.ignorerSelector); + } element.draggable = true; element.addEventListener('dragstart', this.handleDragStart); } destroyItem(element: HTMLElement) { - destroyIgnorerElements(element, this.#config.ignorerSelector); + if (this.#config.ignorerSelector) { + destroyIgnorerElements(element, this.#config.ignorerSelector); + } element.removeEventListener('dragstart', this.handleDragStart); } @@ -271,7 +310,11 @@ export class UmbSorterController implements UmbControllerInterface { if (movingItemIndex < this.#model.length - 1) { const afterItem = this.#model[movingItemIndex + 1]; const afterEl = this.#config.querySelectModelToElement(this.#host, afterItem); - this.#host.insertBefore(this.#currentElement, afterEl); + if (afterEl) { + this.#host.insertBefore(this.#currentElement, afterEl); + } else { + this.#host.appendChild(this.#currentElement); + } } else { this.#host.appendChild(this.#currentElement); } @@ -285,7 +328,7 @@ export class UmbSorterController implements UmbControllerInterface { cancelAnimationFrame(this.#rqaId); } - this.#currentContainerElement = this.#host; + this.#currentContainerElement = this.#containerElement; this.#currentContainerVM = this; this.#rqaId = undefined; @@ -315,7 +358,7 @@ export class UmbSorterController implements UmbControllerInterface { this.#dragX = clientX; this.#dragY = clientY; - handleAutoScroll(this.#dragX, this.#dragY); + this.handleAutoScroll(this.#dragX, this.#dragY); this.#currentDragRect = this.#currentDragElement!.getBoundingClientRect(); const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, this.#currentDragRect); @@ -343,7 +386,7 @@ export class UmbSorterController implements UmbControllerInterface { const currentBoundaryElement = (this.#config.boundarySelector ? this.#currentContainerElement.closest(this.#config.boundarySelector) - : this.#currentContainerElement) || this.#currentContainerElement; + : this.#currentContainerElement) ?? this.#currentContainerElement; const currentBoundaryRect = currentBoundaryElement.getBoundingClientRect(); @@ -355,18 +398,22 @@ export class UmbSorterController implements UmbControllerInterface { 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. const parentNode = this.#currentContainerElement.parentNode; - const parentContainer = parentNode ? (parentNode as HTMLElement).closest(this.#config.containerSelector) : null; - if (parentContainer) { - const parentContainerVM = (parentContainer as any)['__umbBlockGridSorterController'](); - if (parentContainerVM.unique === this.unique) { - this.#currentContainerElement = parentContainer; - this.#currentContainerVM = parentContainerVM; - if (this.#config.onContainerChange) { - this.#config.onContainerChange({ - item: this.#currentItem, - element: this.#currentElement, - //ownerVM: this.#currentContainerVM.ownerVM, - }); + if (parentNode) { + const parentContainer = this.#config.containerSelector + ? (parentNode as HTMLElement).closest(this.#config.containerSelector) + : null; + if (parentContainer) { + const parentContainerVM = (parentContainer as any)['__umbBlockGridSorterController'](); + if (parentContainerVM.unique === this.unique) { + this.#currentContainerElement = parentContainer as Element; + this.#currentContainerVM = parentContainerVM; + if (this.#config.onContainerChange) { + this.#config.onContainerChange({ + item: this.#currentItem, + element: this.#currentElement, + //ownerVM: this.#currentContainerVM.ownerVM, + }); + } } } } @@ -736,6 +783,7 @@ export class UmbSorterController implements UmbControllerInterface { (this.#host as any)['__umbBlockGridSorterController'] = null; this.#host.removeEventListener('dragover', preventDragOver); + // TODO: Clean up items?? this.#observer.disconnect(); // For auto scroller: