Вложенное перетаскивание с помощью Angular 7 Material CDK
У меня есть вложенное дерево (не компонент дерева) списков перетаскивания.
При перетаскивании элементов в перетаскиваемых списках, которые содержатся внутри другого выпадающего списка - события Enter / Exit запускаются для обоих выпадающих списков, что означает, что при удалении элемента его можно было либо перетащить во внутренний перетаскивающий список, либо в раскрывающийся список контейнера в зависимости от где он был удален (Примечание: все эти списки связаны друг с другом)
Сейчас я думаю, что лучшим решением будет подавление событий, запускаемых для списка контейнеров, если в данный момент перетаскивание происходит по внутреннему списку, но я не уверен, является ли это лучшим решением или как именно это сделать на момент.
1 ответ
Мне удалось найти решение этой проблемы, хотя оно определенно хакерское и предполагает доступ к закрытому значению с помощью Angular drag and drop CDK.
Я использую функцию cdkDropListEnterPredicate, чтобы проверить, в какой список следует пытаться попасть, и назначить функцию canDropPredicate.
Я также вынужден получить доступ к позиции указателя через: _pointerPositionAtLastDirectionChange, что не очень хорошо, так как не все значения, которые я хотел бы видеть, переданные в cdkDropListEnterPredicate, будут переданы.
canDropPredicate(): Function {
const me = this;
return (drag: CdkDrag<ResourceNode>, drop: CdkDropList<ResourceNode>): boolean => {
const fromBounds = drag.dropContainer.element.nativeElement.getBoundingClientRect();
const toBounds = drop.element.nativeElement.getBoundingClientRect();
if (!me.intersect(fromBounds, toBounds)) {
return true;
}
// This gross but allows us to access a private field for now.
const pointerPosition: Point = drag['_pointerPositionAtLastDirectionChange'];
// They Intersect with each other so we need to do some calculations here.
if (me.insideOf(fromBounds, toBounds)) {
return !me.pointInsideOf(pointerPosition, fromBounds);
}
if (me.insideOf(toBounds, fromBounds) && me.pointInsideOf(pointerPosition, toBounds)) {
return true;
}
return false;
};
}
intersect(r1: DOMRect | ClientRect, r2: DOMRect | ClientRect): boolean {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
insideOf(innerRect: DOMRect | ClientRect, outerRect: DOMRect | ClientRect): boolean {
return innerRect.left >= outerRect.left &&
innerRect.right <= outerRect.right &&
innerRect.top >= outerRect.top &&
innerRect.bottom <= outerRect.bottom &&
!(
innerRect.left === outerRect.left &&
innerRect.right === outerRect.right &&
innerRect.top === outerRect.top &&
innerRect.bottom === outerRect.bottom
);
}
pointInsideOf(position: Point, rect: DOMRect | ClientRect) {
return position.x >= rect.left &&
position.x <= rect.right &&
position.y >= rect.top &&
position.y <= rect.bottom;
}
Проблема с этим решением заключается в том, что предикат не влияет на уже продолжающееся перетаскивание в CdkDropList, где оно было создано.
Это проблема, потому что при перемещении объектов "вверх" иерархия работает (из-за функции предиката), перемещение, например, одного элемента под одноуровневым элементом "вниз" в иерархии вызывает неизбежное перекрытие 2 областей CdkDropLists, что приведет к гонке состояние.
Это основная проблема, которую нужно решить - как произвольно сказать родительскому CdkDropList "перестать учитывать" CdkDrag (реагируя на перетаскивание), при этом все еще имея возможность эффективно отбросить его в дочернем CdkDropList
Или другими словами - получить все списки CdkDropList, проколотые по оси Z X,Y помеченные позицией указателя, выбрать один с наименьшей площадью ("младшая") и сделать остальные "не реагирующими" на текущее перетаскивание.