Fix: Improve sorter placement algorithm (#18021)
* improve sorting algorithm * fix block type input * make confirm modal localizable * rename method * clean up * clean up * improve code * Fix creating Block Types in Groups * remove #moveData * lint fixes * remove unused --------- Co-authored-by: Mads Rasmussen <madsr@hey.com>
This commit is contained in:
@@ -2484,8 +2484,8 @@ export default {
|
||||
confirmDeleteBlockTypeMessage: 'Are you sure you want to delete the block configuration <strong>%0%</strong>?',
|
||||
confirmDeleteBlockTypeNotice:
|
||||
'The content of this block will still be present, editing of this content\n will no longer be available and will be shown as unsupported content.\n ',
|
||||
confirmDeleteBlockGroupMessage:
|
||||
'Are you sure you want to delete group <strong>%0%</strong> and all the Block configurations of this?',
|
||||
confirmDeleteBlockGroupTitle: 'Delete group?',
|
||||
confirmDeleteBlockGroupMessage: 'Are you sure you want to delete group <strong>%0%</strong>?',
|
||||
confirmDeleteBlockGroupNotice:
|
||||
'The content of these Blocks will still be present, editing of this content\n will no longer be available and will be shown as unsupported content.\n ',
|
||||
blockConfigurationOverlayTitle: "Configuration of '%0%'",
|
||||
|
||||
@@ -10,7 +10,11 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { html, customElement, state, repeat, css, property, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import '../block-grid-entry/index.js';
|
||||
import { UmbSorterController, type UmbSorterConfig, type resolvePlacementArgs } from '@umbraco-cms/backoffice/sorter';
|
||||
import {
|
||||
UmbSorterController,
|
||||
type UmbSorterConfig,
|
||||
type UmbSorterResolvePlacementArgs,
|
||||
} from '@umbraco-cms/backoffice/sorter';
|
||||
import {
|
||||
UmbFormControlMixin,
|
||||
UmbFormControlValidator,
|
||||
@@ -23,7 +27,9 @@ import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models';
|
||||
* @param args
|
||||
* @returns { null | true }
|
||||
*/
|
||||
function resolvePlacementAsGrid(args: resolvePlacementArgs<UmbBlockGridLayoutModel, UmbBlockGridEntryElement>) {
|
||||
function resolvePlacementAsBlockGrid(
|
||||
args: UmbSorterResolvePlacementArgs<UmbBlockGridLayoutModel, UmbBlockGridEntryElement>,
|
||||
) {
|
||||
// If this has areas, we do not want to move, unless we are at the edge
|
||||
if (args.relatedModel.areas?.length > 0 && isWithinRect(args.pointerX, args.pointerY, args.relatedRect, -10)) {
|
||||
return null;
|
||||
@@ -80,9 +86,16 @@ function resolvePlacementAsGrid(args: resolvePlacementArgs<UmbBlockGridLayoutMod
|
||||
const relatedStartCol = Math.round(
|
||||
getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns),
|
||||
);
|
||||
|
||||
// If the found related element does not have enough room after which for the current element, then we go vertical mode:
|
||||
return relatedStartCol + (args.horizontalPlaceAfter ? foundElColumns : 0) + currentElementColumns > gridColumnNumber;
|
||||
const verticalDirection = relatedStartCol + foundElColumns + currentElementColumns > gridColumnNumber;
|
||||
return verticalDirection;
|
||||
/*
|
||||
let placeAfter = args.horizontalPlaceAfter;
|
||||
|
||||
return {
|
||||
verticalDirection,
|
||||
placeAfter,
|
||||
};*/
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
@@ -96,7 +109,7 @@ const SORTER_CONFIG: UmbSorterConfig<UmbBlockGridLayoutModel, UmbBlockGridEntryE
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.contentKey;
|
||||
},
|
||||
resolvePlacement: resolvePlacementAsGrid,
|
||||
resolvePlacement: resolvePlacementAsBlockGrid,
|
||||
identifier: 'block-grid-editor',
|
||||
itemSelector: 'umb-block-grid-entry',
|
||||
containerSelector: '.umb-block-grid__layout-container',
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { UmbBlockTypeWithGroupKey, UmbInputBlockTypeElement } from '../../../block-type/index.js';
|
||||
import '../../../block-type/components/input-block-type/index.js';
|
||||
import {
|
||||
type UmbPropertyEditorUiElement,
|
||||
UmbPropertyValueChangeEvent,
|
||||
type UmbPropertyEditorConfigCollection,
|
||||
import type { UmbBlockTypeWithGroupKey, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type';
|
||||
import type {
|
||||
UmbPropertyEditorUiElement,
|
||||
UmbPropertyEditorConfigCollection,
|
||||
} from '@umbraco-cms/backoffice/property-editor';
|
||||
import {
|
||||
html,
|
||||
@@ -30,6 +28,8 @@ import {
|
||||
} from '@umbraco-cms/backoffice/property';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
interface MappedGroupWithBlockTypes extends UmbBlockGridTypeGroupType {
|
||||
blocks: Array<UmbBlockTypeWithGroupKey>;
|
||||
@@ -43,7 +43,6 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
extends UmbLitElement
|
||||
implements UmbPropertyEditorUiElement
|
||||
{
|
||||
#moveData?: Array<UmbBlockTypeWithGroupKey>;
|
||||
#sorter = new UmbSorterController<MappedGroupWithBlockTypes, HTMLElement>(this, {
|
||||
getUniqueOfElement: (element) => element.getAttribute('data-umb-group-key'),
|
||||
getUniqueOfModel: (modelEntry) => modelEntry.key!,
|
||||
@@ -104,8 +103,14 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
|
||||
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => {
|
||||
this.#datasetContext = context;
|
||||
//this.#observeBlocks();
|
||||
this.#observeBlockGroups();
|
||||
this.observe(
|
||||
await this.#datasetContext.propertyValueByAlias('blockGroups'),
|
||||
(value) => {
|
||||
this.#blockGroups = (value as Array<UmbBlockGridTypeGroupType>) ?? [];
|
||||
this.#mapValuesToBlockGroups();
|
||||
},
|
||||
'_observeBlockGroups',
|
||||
);
|
||||
});
|
||||
|
||||
this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(
|
||||
@@ -119,24 +124,6 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
});
|
||||
}
|
||||
|
||||
async #observeBlockGroups() {
|
||||
if (!this.#datasetContext) return;
|
||||
this.observe(await this.#datasetContext.propertyValueByAlias('blockGroups'), (value) => {
|
||||
this.#blockGroups = (value as Array<UmbBlockGridTypeGroupType>) ?? [];
|
||||
this.#mapValuesToBlockGroups();
|
||||
});
|
||||
}
|
||||
// TODO: No need for this, we just got the value via the value property.. [NL]
|
||||
/*
|
||||
async #observeBlocks() {
|
||||
if (!this.#datasetContext) return;
|
||||
this.observe(await this.#datasetContext.propertyValueByAlias('blocks'), (value) => {
|
||||
this.value = (value as Array<UmbBlockTypeWithGroupKey>) ?? [];
|
||||
this.#mapValuesToBlockGroups();
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
#mapValuesToBlockGroups() {
|
||||
if (!this.#blockGroups) return;
|
||||
// Map blocks that are not in any group, or in a group that does not exist
|
||||
@@ -152,63 +139,60 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
this.#sorter.setModel(this._groupsWithBlockTypes);
|
||||
}
|
||||
|
||||
#onDelete(e: CustomEvent, groupKey?: string) {
|
||||
const updatedValues = (e.target as UmbInputBlockTypeElement).value.map((value) => ({ ...value, groupKey }));
|
||||
const filteredValues = this.#value.filter((value) => value.groupKey !== groupKey);
|
||||
this.value = [...filteredValues, ...updatedValues];
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
async #onChange(e: CustomEvent) {
|
||||
async #onChange(e: Event, groupKey?: string) {
|
||||
e.stopPropagation();
|
||||
const element = e.target as UmbInputBlockTypeElement;
|
||||
const value = element.value;
|
||||
const value = element.value.map((x) => ({ ...x, groupKey }));
|
||||
|
||||
if (!e.detail?.moveComplete) {
|
||||
// Container change, store data of the new group...
|
||||
const newGroupKey = element.getAttribute('data-umb-group-key');
|
||||
const movedItem = e.detail?.item as UmbBlockTypeWithGroupKey;
|
||||
// Check if item moved back to original group...
|
||||
if (movedItem.groupKey === newGroupKey) {
|
||||
this.#moveData = undefined;
|
||||
} else {
|
||||
this.#moveData = value.map((block) => ({ ...block, groupKey: newGroupKey }));
|
||||
}
|
||||
} else if (e.detail?.moveComplete) {
|
||||
// Move complete, get the blocks that were in an untouched group
|
||||
const blocks = this.#value
|
||||
.filter((block) => !value.find((value) => value.contentElementTypeKey === block.contentElementTypeKey))
|
||||
.filter(
|
||||
(block) => !this.#moveData?.find((value) => value.contentElementTypeKey === block.contentElementTypeKey),
|
||||
);
|
||||
|
||||
this.value = this.#moveData ? [...blocks, ...value, ...this.#moveData] : [...blocks, ...value];
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
this.#moveData = undefined;
|
||||
if (groupKey) {
|
||||
// Update the specific group:
|
||||
this._groupsWithBlockTypes = this._groupsWithBlockTypes.map((group) => {
|
||||
if (group.key === groupKey) {
|
||||
return { ...group, blocks: value };
|
||||
}
|
||||
return group;
|
||||
});
|
||||
} else {
|
||||
// Update the not grouped blocks:
|
||||
this._notGroupedBlockTypes = value;
|
||||
}
|
||||
|
||||
this.#updateValue();
|
||||
}
|
||||
|
||||
#updateValue() {
|
||||
this.value = [...this._notGroupedBlockTypes, ...this._groupsWithBlockTypes.flatMap((group) => group.blocks)];
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
#updateBlockGroupsValue(groups: Array<UmbBlockGridTypeGroupType>) {
|
||||
this.#datasetContext?.setPropertyValue('blockGroups', groups);
|
||||
}
|
||||
|
||||
#onCreate(e: CustomEvent, groupKey?: string) {
|
||||
const selectedElementType = e.detail.contentElementTypeKey;
|
||||
if (selectedElementType) {
|
||||
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/' + (groupKey ?? null));
|
||||
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/' + (groupKey ?? 'null'));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement confirm dialog [NL]
|
||||
#deleteGroup(groupKey: string) {
|
||||
// TODO: make one method for updating the blockGroupsDataSetValue: [NL]
|
||||
// This one that deletes might require the ability to parse what to send as an argument to the method, then a filtering can occur before.
|
||||
this.#datasetContext?.setPropertyValue(
|
||||
'blockGroups',
|
||||
this.#blockGroups?.filter((group) => group.key !== groupKey),
|
||||
);
|
||||
|
||||
async #deleteGroup(groupKey: string) {
|
||||
const groupName = this.#blockGroups?.find((group) => group.key === groupKey)?.name ?? '';
|
||||
await umbConfirmModal(this, {
|
||||
headline: '#blockEditor_confirmDeleteBlockGroupTitle',
|
||||
content: this.localize.term('#blockEditor_confirmDeleteBlockGroupMessage', [groupName]),
|
||||
color: 'danger',
|
||||
confirmLabel: '#general_delete',
|
||||
});
|
||||
// If a group is deleted, Move the blocks to no group:
|
||||
this.value = this.#value.map((block) => (block.groupKey === groupKey ? { ...block, groupKey: undefined } : block));
|
||||
if (this.#blockGroups) {
|
||||
this.#updateBlockGroupsValue(this.#blockGroups.filter((group) => group.key !== groupKey));
|
||||
}
|
||||
}
|
||||
|
||||
#changeGroupName(e: UUIInputEvent, groupKey: string) {
|
||||
#onGroupNameChange(e: UUIInputEvent, groupKey: string) {
|
||||
const groupName = e.target.value as string;
|
||||
// TODO: make one method for updating the blockGroupsDataSetValue: [NL]
|
||||
this.#datasetContext?.setPropertyValue(
|
||||
@@ -224,9 +208,8 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
.propertyAlias=${this._alias}
|
||||
.value=${this._notGroupedBlockTypes}
|
||||
.workspacePath=${this._workspacePath}
|
||||
@change=${this.#onChange}
|
||||
@create=${(e: CustomEvent) => this.#onCreate(e, undefined)}
|
||||
@delete=${(e: CustomEvent) => this.#onDelete(e, undefined)}></umb-input-block-type>`
|
||||
@change=${(e: Event) => this.#onChange(e, undefined)}
|
||||
@create=${(e: CustomEvent) => this.#onCreate(e, undefined)}></umb-input-block-type>`
|
||||
: ''}
|
||||
${repeat(
|
||||
this._groupsWithBlockTypes,
|
||||
@@ -239,9 +222,8 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
.propertyAlias=${this._alias + '_' + group.key}
|
||||
.value=${group.blocks}
|
||||
.workspacePath=${this._workspacePath}
|
||||
@change=${this.#onChange}
|
||||
@create=${(e: CustomEvent) => this.#onCreate(e, group.key)}
|
||||
@delete=${(e: CustomEvent) => this.#onDelete(e, group.key)}></umb-input-block-type>
|
||||
@change=${(e: Event) => this.#onChange(e, group.key)}
|
||||
@create=${(e: CustomEvent) => this.#onCreate(e, group.key)}></umb-input-block-type>
|
||||
</div>`,
|
||||
)}
|
||||
</div>`;
|
||||
@@ -253,7 +235,7 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
auto-width
|
||||
label="Group"
|
||||
.value=${groupName ?? ''}
|
||||
@change=${(e: UUIInputEvent) => this.#changeGroupName(e, groupKey)}>
|
||||
@change=${(e: UUIInputEvent) => this.#onGroupNameChange(e, groupKey)}>
|
||||
<uui-button compact slot="append" label="delete" @click=${() => this.#deleteGroup(groupKey)}>
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
|
||||
@@ -6,12 +6,14 @@ import { css, html, customElement, property, state, repeat } from '@umbraco-cms/
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
import { UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import {
|
||||
UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT,
|
||||
UMB_DOCUMENT_TYPE_PICKER_MODAL,
|
||||
type UmbDocumentTypePickerModalData,
|
||||
type UmbDocumentTypePickerModalValue,
|
||||
} from '@umbraco-cms/backoffice/document-type';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController, UmbSorterResolvePlacementAsGrid } from '@umbraco-cms/backoffice/sorter';
|
||||
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
|
||||
|
||||
import '../block-type-card/index.js';
|
||||
@@ -27,32 +29,40 @@ export class UmbInputBlockTypeElement<
|
||||
itemSelector: 'umb-block-type-card',
|
||||
identifier: 'umb-block-type-sorter',
|
||||
containerSelector: '#blocks',
|
||||
resolvePlacement: UmbSorterResolvePlacementAsGrid,
|
||||
onContainerChange: ({ item, model }) => {
|
||||
this.dispatchEvent(new CustomEvent('container-change', { detail: { item, model } }));
|
||||
},
|
||||
onChange: ({ model }) => {
|
||||
this._items = model;
|
||||
this._value = model;
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
},
|
||||
onContainerChange: ({ model, item }) => {
|
||||
this._items = model;
|
||||
this.dispatchEvent(new CustomEvent('change', { detail: { item } }));
|
||||
},
|
||||
onEnd: () => {
|
||||
/*onEnd: () => {
|
||||
// TODO: Investigate if onEnd is called when a container move has been performed, if not then I would say it should be. [NL]
|
||||
this.dispatchEvent(new CustomEvent('change', { detail: { moveComplete: true } }));
|
||||
},
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
},*/
|
||||
});
|
||||
#elementPickerModal: UmbModalRouteRegistrationController<
|
||||
UmbDocumentTypePickerModalData,
|
||||
UmbDocumentTypePickerModalValue
|
||||
>;
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
public set value(items) {
|
||||
this._items = items ?? [];
|
||||
this.#sorter.setModel(this._items);
|
||||
this._value = items ?? [];
|
||||
// Make sure the block types are unique on contentTypeElementKey:
|
||||
this._value = this._value.filter(
|
||||
(value, index, self) => self.findIndex((x) => x.contentElementTypeKey === value.contentElementTypeKey) === index,
|
||||
);
|
||||
this.#sorter.setModel(this._value);
|
||||
}
|
||||
public get value() {
|
||||
return this._items;
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/** @deprecated will be removed in v17 */
|
||||
@property({ type: String })
|
||||
public set propertyAlias(value: string | undefined) {
|
||||
//this.#elementPickerModal.setUniquePathValue('propertyAlias', value);
|
||||
this.#elementPickerModal.setUniquePathValue('propertyAlias', value);
|
||||
}
|
||||
public get propertyAlias(): string | undefined {
|
||||
return undefined;
|
||||
@@ -65,7 +75,7 @@ export class UmbInputBlockTypeElement<
|
||||
private _pickerPath?: string;
|
||||
|
||||
@state()
|
||||
private _items: Array<BlockType> = [];
|
||||
private _value: Array<BlockType> = [];
|
||||
|
||||
// TODO: Seems no need to have these initially, then can be retrieved inside the `create` method. [NL]
|
||||
#datasetContext?: UmbPropertyDatasetContext;
|
||||
@@ -84,7 +94,8 @@ export class UmbInputBlockTypeElement<
|
||||
);
|
||||
});
|
||||
|
||||
new UmbModalRouteRegistrationController(this, UMB_DOCUMENT_TYPE_PICKER_MODAL)
|
||||
this.#elementPickerModal = new UmbModalRouteRegistrationController(this, UMB_DOCUMENT_TYPE_PICKER_MODAL)
|
||||
.addUniquePaths(['propertyAlias'])
|
||||
.onSetup(() => {
|
||||
return {
|
||||
data: {
|
||||
@@ -123,8 +134,8 @@ export class UmbInputBlockTypeElement<
|
||||
}
|
||||
|
||||
deleteItem(contentElementTypeKey: string) {
|
||||
this.value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
|
||||
this.dispatchEvent(new UmbDeleteEvent());
|
||||
this._value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
async #onRequestDelete(item: BlockType) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UmbRefItemElement } from '@umbraco-cms/backoffice/components';
|
||||
import type {
|
||||
UmbEntityCreateOptionActionListModalData,
|
||||
UmbEntityCreateOptionActionListModalValue,
|
||||
} from './entity-create-option-action-list-modal.token.js';
|
||||
import { UmbRefItemElement } from '@umbraco-cms/backoffice/components';
|
||||
import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';
|
||||
import type { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, customElement, property, css, unsafeHTML } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbConfirmModalData, UmbConfirmModalValue, UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -21,20 +21,20 @@ export class UmbConfirmModalElement extends UmbLitElement {
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<uui-dialog-layout class="uui-text" .headline=${this.data?.headline || null}>
|
||||
${this.data?.content}
|
||||
<uui-dialog-layout class="uui-text" .headline=${this.localize.string(this.data?.headline) ?? null}>
|
||||
${unsafeHTML(this.localize.string(this.data?.content))}
|
||||
|
||||
<uui-button
|
||||
slot="actions"
|
||||
id="cancel"
|
||||
label=${this.data?.cancelLabel || this.localize.term('buttons_confirmActionCancel')}
|
||||
label=${this.localize.string(this.data?.cancelLabel ?? '#buttons_confirmActionCancel')}
|
||||
@click=${this._handleCancel}></uui-button>
|
||||
<uui-button
|
||||
slot="actions"
|
||||
id="confirm"
|
||||
color=${this.data?.color || 'positive'}
|
||||
look="primary"
|
||||
label=${this.data?.confirmLabel || this.localize.term('buttons_confirmActionConfirm')}
|
||||
label=${this.localize.string(this.data?.confirmLabel ?? '#buttons_confirmActionConfirm')}
|
||||
@click=${this._handleConfirm}
|
||||
${umbFocus()}></uui-button>
|
||||
</uui-dialog-layout>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './sorter.controller.js';
|
||||
export * from './replacement-resolver-as-grid.function.js';
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { UmbSorterResolvePlacementArgs } from './sorter.controller.js';
|
||||
|
||||
/**
|
||||
* This function is used to resolve the placement of an item in a simple grid layout.
|
||||
* @param args
|
||||
* @returns { null | true }
|
||||
*/
|
||||
export function UmbSorterResolvePlacementAsGrid(args: UmbSorterResolvePlacementArgs<unknown>) {
|
||||
// If we are part of the same Sorter model
|
||||
if (args.itemIndex !== null && args.relatedIndex !== null) {
|
||||
// and the pointer is within the related rect
|
||||
if (args.relatedRect.left < args.pointerX && args.relatedRect.right > args.pointerX) {
|
||||
// Then we control the placeAfter property, making the active-drag-element allow to be placed at a spot already when just hovering that spot. (This only works when items have the same size)
|
||||
return {
|
||||
placeAfter: args.itemIndex < args.relatedIndex,
|
||||
};
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -80,21 +80,39 @@ function destroyPreventEvent(element: Element) {
|
||||
//element.removeAttribute('draggable');
|
||||
}
|
||||
|
||||
export type resolvePlacementArgs<T, ElementType extends HTMLElement> = {
|
||||
export type UmbSorterResolvePlacementReturn =
|
||||
| boolean
|
||||
| null
|
||||
| {
|
||||
placeAfter: boolean;
|
||||
verticalDirection?: boolean;
|
||||
};
|
||||
|
||||
export type UmbSorterResolvePlacementArgs<T, ElementType extends HTMLElement = HTMLElement> = {
|
||||
containerElement: Element;
|
||||
containerRect: DOMRect;
|
||||
item: T;
|
||||
itemIndex: number | null;
|
||||
element: ElementType;
|
||||
elementRect: DOMRect;
|
||||
relatedElement: ElementType;
|
||||
relatedModel: T;
|
||||
relatedRect: DOMRect;
|
||||
relatedIndex: number | null;
|
||||
placeholderIsInThisRow: boolean;
|
||||
horizontalPlaceAfter: boolean;
|
||||
pointerX: number;
|
||||
pointerY: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated will be removed in v.17, use `UmbSorterResolvePlacementArgs`
|
||||
*/
|
||||
export type resolvePlacementArgs<T, ElementType extends HTMLElement = HTMLElement> = UmbSorterResolvePlacementArgs<
|
||||
T,
|
||||
ElementType
|
||||
>;
|
||||
|
||||
type UniqueType = string | symbol | number;
|
||||
|
||||
/**
|
||||
@@ -180,7 +198,7 @@ type INTERNAL_UmbSorterConfig<T, ElementType extends HTMLElement> = {
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
resolvePlacement?: (argument: resolvePlacementArgs<T, ElementType>) => boolean | null;
|
||||
resolvePlacement?: (argument: UmbSorterResolvePlacementArgs<T, ElementType>) => UmbSorterResolvePlacementReturn;
|
||||
/**
|
||||
* This callback is executed when an item is moved within this container.
|
||||
*/
|
||||
@@ -248,7 +266,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
#observer;
|
||||
|
||||
#model: Array<T> = [];
|
||||
#rqaId?: number;
|
||||
static rqaId?: number;
|
||||
|
||||
#containerElement!: HTMLElement;
|
||||
#useContainerShadowRoot?: boolean;
|
||||
@@ -260,7 +278,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
#dragX = 0;
|
||||
#dragY = 0;
|
||||
|
||||
#items = Array<ElementType>();
|
||||
#elements = Array<ElementType>();
|
||||
|
||||
public get identifier() {
|
||||
return this.#config.identifier;
|
||||
@@ -330,7 +348,6 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
setModel(model: Array<T>): void {
|
||||
if (this.#model) {
|
||||
// TODO: Some updates might need to be done, as the model is about to change? Do make the changes after setting the model?.. [NL]
|
||||
this.#model = model;
|
||||
}
|
||||
}
|
||||
@@ -409,7 +426,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
(this.#containerElement as unknown) = undefined;
|
||||
}
|
||||
|
||||
this.#items.forEach((item) => this.destroyItem(item));
|
||||
this.#elements.forEach((item) => this.destroyItem(item));
|
||||
}
|
||||
|
||||
#itemDraggedOver = (e: DragEvent) => {
|
||||
@@ -433,7 +450,11 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
return;
|
||||
} else {
|
||||
// TODO: Check if dropping here is okay..
|
||||
// Indication if drop is good:
|
||||
if (this.updateAllowIndication(UmbSorterController.activeItem) === false) {
|
||||
console.log('Dropping here was not allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
// If so lets set the approaching sorter:
|
||||
UmbSorterController.dropSorter = this as unknown as UmbSorterController<unknown>;
|
||||
@@ -476,8 +497,8 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
}
|
||||
}
|
||||
|
||||
this.#items.push(element);
|
||||
this.#items = Array.from(new Set(this.#items));
|
||||
this.#elements.push(element);
|
||||
this.#elements = Array.from(new Set(this.#elements));
|
||||
}
|
||||
|
||||
destroyItem(element: HTMLElement) {
|
||||
@@ -491,7 +512,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
(draggableElement as HTMLElement).draggable = false;
|
||||
|
||||
this.#items = this.#items.filter((x) => x !== element);
|
||||
this.#elements = this.#elements.filter((x) => x !== element);
|
||||
}
|
||||
|
||||
#setupPlaceholderStyle() {
|
||||
@@ -586,9 +607,9 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
UmbSorterController.dropSorter = this as unknown as UmbSorterController<unknown>;
|
||||
|
||||
// We must wait one frame before changing the look of the block.
|
||||
this.#rqaId = requestAnimationFrame(() => {
|
||||
UmbSorterController.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;
|
||||
UmbSorterController.rqaId = undefined;
|
||||
if (UmbSorterController.activeElement) {
|
||||
UmbSorterController.activeElement.style.transform = '';
|
||||
}
|
||||
@@ -650,9 +671,9 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
});
|
||||
}
|
||||
|
||||
if (this.#rqaId) {
|
||||
cancelAnimationFrame(this.#rqaId);
|
||||
this.#rqaId = undefined;
|
||||
if (UmbSorterController.rqaId) {
|
||||
cancelAnimationFrame(UmbSorterController.rqaId);
|
||||
UmbSorterController.rqaId = undefined;
|
||||
}
|
||||
|
||||
UmbSorterController.activeItem = undefined;
|
||||
@@ -689,19 +710,23 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
const activeDragRect = UmbSorterController.activeDragElement!.getBoundingClientRect();
|
||||
const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, activeDragRect);
|
||||
if (!insideCurrentRect) {
|
||||
if (this.#rqaId === undefined) {
|
||||
this.#rqaId = requestAnimationFrame(this.#updateDragMove);
|
||||
if (UmbSorterController.rqaId === undefined) {
|
||||
UmbSorterController.rqaId = requestAnimationFrame(this.#updateDragMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#updateDragMove = () => {
|
||||
this.#rqaId = undefined;
|
||||
UmbSorterController.rqaId = undefined;
|
||||
if (!UmbSorterController.activeElement || !UmbSorterController.activeItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((UmbSorterController.dropSorter as any) !== this) {
|
||||
throw new Error('Drop sorter is not this sorter');
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -744,6 +769,10 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
elementsInSameRow.forEach((sameRow) => {
|
||||
const centerX = sameRow.dragRect.left + sameRow.dragRect.width * 0.5;
|
||||
const distance = Math.abs(this.#dragX - centerX);
|
||||
/*const distance = Math.min(
|
||||
Math.abs(this.#dragX - sameRow.dragRect.left),
|
||||
Math.abs(this.#dragX - sameRow.dragRect.right),
|
||||
);*/
|
||||
if (distance < lastDistance) {
|
||||
foundEl = sameRow.el as HTMLElement;
|
||||
foundElDragRect = sameRow.dragRect;
|
||||
@@ -752,6 +781,11 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
}
|
||||
});
|
||||
|
||||
let activeIndex: number | null = this.#model.indexOf(UmbSorterController.activeItem);
|
||||
if (activeIndex === -1) {
|
||||
activeIndex = null;
|
||||
}
|
||||
|
||||
if (foundEl) {
|
||||
// If we are on top or closest to our self, we should not do anything.
|
||||
if (foundEl === UmbSorterController.activeElement) {
|
||||
@@ -763,21 +797,35 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
throw new Error('Could not find model of found element');
|
||||
}
|
||||
|
||||
// Indication if drop is good:
|
||||
if (this.updateAllowIndication(UmbSorterController.activeItem) === false) {
|
||||
return;
|
||||
let relatedIndex: number | null = this.#model.indexOf(foundModel);
|
||||
if (relatedIndex === -1) {
|
||||
relatedIndex = null;
|
||||
}
|
||||
|
||||
const verticalDirection: boolean | null = this.#config.resolvePlacement
|
||||
if (activeIndex !== null && relatedIndex !== null) {
|
||||
// We have both indexes, aka. both elements are in this list.
|
||||
const widthDiff = Math.max(foundElDragRect.width - currentElementRect.width, 0);
|
||||
if (activeIndex < relatedIndex && foundElDragRect.left + widthDiff < this.#dragX) {
|
||||
// If we are located before and we are just enough over to get located after, then lets do it already.
|
||||
placeAfter = true;
|
||||
} else if (activeIndex > relatedIndex && foundElDragRect.right - widthDiff > this.#dragX) {
|
||||
// If we are located after and we are just enough over to get located before, then lets do it already.
|
||||
placeAfter = false;
|
||||
}
|
||||
}
|
||||
|
||||
const placementResult: UmbSorterResolvePlacementReturn = this.#config.resolvePlacement
|
||||
? this.#config.resolvePlacement({
|
||||
containerElement: this.#containerElement,
|
||||
containerRect: currentContainerRect,
|
||||
item: UmbSorterController.activeItem,
|
||||
itemIndex: activeIndex,
|
||||
element: UmbSorterController.activeElement as ElementType,
|
||||
elementRect: currentElementRect,
|
||||
relatedElement: foundEl,
|
||||
relatedModel: foundModel,
|
||||
relatedRect: foundElDragRect,
|
||||
relatedIndex: relatedIndex,
|
||||
placeholderIsInThisRow: placeholderIsInThisRow,
|
||||
horizontalPlaceAfter: placeAfter,
|
||||
pointerX: this.#dragX,
|
||||
@@ -785,20 +833,41 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
})
|
||||
: true;
|
||||
|
||||
if (verticalDirection === null) {
|
||||
// The resolvePlacement has chosen to back out of this move.
|
||||
if (placementResult === null) {
|
||||
// The resolvePlacement method has chosen to back out of this move.
|
||||
return;
|
||||
}
|
||||
|
||||
if (verticalDirection) {
|
||||
placeAfter = this.#dragY > foundElDragRect.top + foundElDragRect.height * 0.5;
|
||||
let verticalDirection = true;
|
||||
if (typeof placementResult === 'object') {
|
||||
verticalDirection = placementResult.verticalDirection ?? false;
|
||||
placeAfter = placementResult.placeAfter;
|
||||
} else {
|
||||
verticalDirection = placementResult ?? false;
|
||||
if (verticalDirection === true) {
|
||||
// Lets check if we should place after or before, based on a vertical algortihm.
|
||||
placeAfter = this.#dragY > foundElDragRect.top + foundElDragRect.height * 0.5;
|
||||
|
||||
// There is room for improvements, if we are in the same model:
|
||||
if (activeIndex !== null && relatedIndex !== null) {
|
||||
// We have both indexes, aka. both elements are in this list.
|
||||
const heightDiff = Math.max(foundElDragRect.height - currentElementRect.height, 0);
|
||||
if (activeIndex < relatedIndex && this.#dragY > foundElDragRect.top + heightDiff) {
|
||||
// If active is located above and we are just enough above to get located after, then lets do it already.
|
||||
placeAfter = true;
|
||||
} else if (activeIndex > relatedIndex && this.#dragY < foundElDragRect.bottom - heightDiff) {
|
||||
// If active is located below and we are just enough above to get located before, then lets do it already.
|
||||
placeAfter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (verticalDirection) {
|
||||
if (verticalDirection === true) {
|
||||
let el;
|
||||
if (placeAfter === false) {
|
||||
let lastLeft = foundElDragRect.left;
|
||||
elementsInSameRow.findIndex((x) => {
|
||||
elementsInSameRow.map((x) => {
|
||||
if (x.dragRect.left < lastLeft) {
|
||||
lastLeft = x.dragRect.left;
|
||||
el = x.el;
|
||||
@@ -806,7 +875,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
});
|
||||
} else {
|
||||
let lastRight = foundElDragRect.right;
|
||||
elementsInSameRow.findIndex((x) => {
|
||||
elementsInSameRow.map((x) => {
|
||||
if (x.dragRect.right > lastRight) {
|
||||
lastRight = x.dragRect.right;
|
||||
el = x.el;
|
||||
@@ -824,7 +893,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
return;
|
||||
}
|
||||
// We skipped the above part cause we are above or below container, or within an empty container:
|
||||
// We skipped the above part cause we are above or below container, or within an empty container, or in a blank space:
|
||||
|
||||
// Indication if drop is good:
|
||||
if (this.updateAllowIndication(UmbSorterController.activeItem) === false) {
|
||||
@@ -838,9 +907,57 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#moveElementTo(0);
|
||||
} else if (this.#dragY > currentContainerRect.bottom) {
|
||||
this.#moveElementTo(-1);
|
||||
} else {
|
||||
// There was no target, but we are still inside. aka. in a vertical gap/gutter/blankspace.
|
||||
if (this.#model.length > 1 && activeIndex !== null) {
|
||||
const belowActive = this.#dragY > currentElementRect.bottom;
|
||||
|
||||
const foundTarget =
|
||||
belowActive === false
|
||||
? this.#findIndexToMoveTo(0, activeIndex)
|
||||
: this.#findIndexToMoveTo(activeIndex, this.#model.length);
|
||||
|
||||
if (foundTarget) {
|
||||
this.#moveElementTo(foundTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#findIndexToMoveTo(a: number, b: number): number | undefined {
|
||||
if (a === b) {
|
||||
return a;
|
||||
}
|
||||
const halfWay = a + Math.round((b - a) * 0.5);
|
||||
|
||||
// if we hit one of the points, then lets just move to the other point.
|
||||
if (halfWay === a || halfWay === b) {
|
||||
return b;
|
||||
}
|
||||
|
||||
const belowHalf = this.#isPointerBelowTargetElement(halfWay);
|
||||
if (belowHalf === null) {
|
||||
throw new Error('Could not determine if below target');
|
||||
}
|
||||
|
||||
if (belowHalf) {
|
||||
return this.#findIndexToMoveTo(halfWay, b);
|
||||
} else {
|
||||
return this.#findIndexToMoveTo(a, halfWay);
|
||||
}
|
||||
}
|
||||
|
||||
#isPointerBelowTargetElement(targetIndex: number) {
|
||||
if (targetIndex > 0 && targetIndex < this.#model.length) {
|
||||
const element = this.getElementOfItem(this.#model[targetIndex]);
|
||||
if (element) {
|
||||
// Below this one == true, otherwise false.
|
||||
return this.#dragY > element?.getBoundingClientRect().bottom;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
async #moveElementTo(newIndex: number) {
|
||||
if (!UmbSorterController.activeElement || !UmbSorterController.activeSorter) {
|
||||
@@ -851,6 +968,9 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
if (!requestingSorter) {
|
||||
throw new Error('Could not find requestingSorter');
|
||||
}
|
||||
if ((requestingSorter as any) !== this) {
|
||||
throw new Error('Requesting sorter is not this sorter');
|
||||
}
|
||||
|
||||
// If same container and same index, do nothing:
|
||||
if (requestingSorter === UmbSorterController.activeSorter && UmbSorterController.activeIndex === newIndex) return;
|
||||
@@ -872,6 +992,16 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
return this.#model.find((entry: T) => elementUnique === this.#config.getUniqueOfModel(entry));
|
||||
}
|
||||
|
||||
public getElementOfItem(item: T) {
|
||||
const unique = this.#config.getUniqueOfModel(item);
|
||||
if (unique === undefined) {
|
||||
console.error('Sorter could not find unique of item', item);
|
||||
//throw new Error('Sorter could not find unique of item');
|
||||
return;
|
||||
}
|
||||
return this.#elements.find((element) => unique === this.#config.getUniqueOfElement(element));
|
||||
}
|
||||
|
||||
public async removeItem(item: T) {
|
||||
if (!item) {
|
||||
return false;
|
||||
@@ -891,32 +1021,11 @@ 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;
|
||||
}
|
||||
|
||||
// TODO: Could get item via attr.
|
||||
public async moveItemInModel(newIndex: number, fromCtrl: UmbSorterController<T, ElementType>) {
|
||||
if (!UmbSorterController.activeItem) {
|
||||
console.error('There is no active item to move');
|
||||
@@ -940,34 +1049,8 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
const localMove = fromCtrl === (this as any);
|
||||
|
||||
if (localMove) {
|
||||
// Local move:
|
||||
|
||||
const oldIndex = this.#model.indexOf(item);
|
||||
if (oldIndex === -1) {
|
||||
console.error('Could not find item in model when performing internal move', this.getHostElement(), this.#model);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.#config.performItemMove) {
|
||||
const result = await this.#config.performItemMove({ item, newIndex, oldIndex });
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const newModel = [...this.#model];
|
||||
newModel.splice(oldIndex, 1);
|
||||
if (oldIndex <= newIndex) {
|
||||
newIndex--;
|
||||
}
|
||||
newModel.splice(newIndex, 0, item);
|
||||
this.#model = newModel;
|
||||
this.#config.onChange?.({ model: newModel, item });
|
||||
}
|
||||
|
||||
UmbSorterController.activeIndex = newIndex;
|
||||
} else {
|
||||
// Not a local move:
|
||||
if (!localMove) {
|
||||
// Not a local move, so we have to switch container to continue:
|
||||
|
||||
if ((await fromCtrl.removeItem(item)) !== true) {
|
||||
console.error('Sync could not remove item when moving to a new container');
|
||||
@@ -997,6 +1080,35 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
UmbSorterController.dropSorter = this as unknown as UmbSorterController<unknown>;
|
||||
UmbSorterController.activeIndex = newIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (localMove) {
|
||||
// Local move:
|
||||
|
||||
const oldIndex = this.#model.indexOf(item);
|
||||
if (oldIndex === -1) {
|
||||
console.error('Could not find item in model when performing internal move', this.getHostElement(), this.#model);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.#config.performItemMove) {
|
||||
const result = await this.#config.performItemMove({ item, newIndex, oldIndex });
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const newModel = [...this.#model];
|
||||
newModel.splice(oldIndex, 1);
|
||||
if (oldIndex <= newIndex) {
|
||||
newIndex--;
|
||||
}
|
||||
newModel.splice(newIndex, 0, item);
|
||||
this.#model = newModel;
|
||||
this.#config.onChange?.({ model: newModel, item });
|
||||
}
|
||||
|
||||
UmbSorterController.activeIndex = newIndex;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController, UmbSorterResolvePlacementAsGrid } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
|
||||
@@ -33,8 +33,7 @@ export class UmbInputMediaElement extends UmbFormControlMixin<string | undefined
|
||||
identifier: 'Umb.SorterIdentifier.InputMedia',
|
||||
itemSelector: 'uui-card-media',
|
||||
containerSelector: '.container',
|
||||
/** TODO: This component probably needs some grid-like logic for resolve placement... [LI] */
|
||||
resolvePlacement: () => false,
|
||||
resolvePlacement: UmbSorterResolvePlacementAsGrid,
|
||||
onChange: ({ model }) => {
|
||||
this.selection = model;
|
||||
this.#sortCards(model);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController, UmbSorterResolvePlacementAsGrid } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router';
|
||||
@@ -25,9 +25,7 @@ type UmbRichMediaCardModel = {
|
||||
isTrashed?: boolean;
|
||||
};
|
||||
|
||||
const elementName = 'umb-input-rich-media';
|
||||
|
||||
@customElement(elementName)
|
||||
@customElement('umb-input-rich-media')
|
||||
export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement, '') {
|
||||
#sorter = new UmbSorterController<UmbMediaPickerPropertyValue>(this, {
|
||||
getUniqueOfElement: (element) => {
|
||||
@@ -39,9 +37,8 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement,
|
||||
identifier: 'Umb.SorterIdentifier.InputRichMedia',
|
||||
itemSelector: 'uui-card-media',
|
||||
containerSelector: '.container',
|
||||
// TODO: This component probably needs some grid-like logic for resolve placement... [LI]
|
||||
// TODO: You can also use verticalDirection? [NL]
|
||||
resolvePlacement: () => false,
|
||||
//resolvePlacement: (args) => args.pointerX < args.relatedRect.left + args.relatedRect.width * 0.5,
|
||||
resolvePlacement: UmbSorterResolvePlacementAsGrid,
|
||||
onChange: ({ model }) => {
|
||||
this.#items = model;
|
||||
this.#sortCards(model);
|
||||
@@ -454,6 +451,6 @@ export default UmbInputRichMediaElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
[elementName]: UmbInputRichMediaElement;
|
||||
'umb-input-rich-media': UmbInputRichMediaElement;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user