[IMP] web: add a maxLevels params for nest mode on nested_sortable hooks

Add a maxLevels param like the jQuery.nestedSortable has. The goal of
this param is to set the maximum depth of nested items the list can
accept. If set to '0' the levels are unlimited, it's the default value.

task-3439226

Part-of: odoo/odoo#136375
This commit is contained in:
Romain Estievenart
2023-10-03 19:20:06 +00:00
parent 6f63e2349c
commit 1dff3912cb
2 changed files with 94 additions and 2 deletions
@@ -34,6 +34,8 @@ import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
* @property {string | () => string} [listTagName] type of lists ("ul" or "ol").
* @property {number | () => number} [nestInterval] Horizontal distance needed to trigger
* a change in the list hierarchy (i.e. changing parent when moving horizontally)
* @property {number | () => number} [maxLevels] The maximum depth of nested items
* the list can accept. If set to '0' the levels are unlimited. Default: 0
*
* HANDLERS (also optional)
*
@@ -78,6 +80,7 @@ export const useNestedSortable = makeDraggableHook({
nest: [Boolean],
listTagName: [String],
nestInterval: [Number],
maxLevels: [Number],
},
defaultParams: {
connectGroups: false,
@@ -89,6 +92,7 @@ export const useNestedSortable = makeDraggableHook({
nest: false,
listTagName: "ul",
nestInterval: 15,
maxLevels: 0,
},
// Set the parameters.
@@ -108,6 +112,7 @@ export const useNestedSortable = makeDraggableHook({
// (i.e. changing parent when moving horizontally)
ctx.nestInterval = params.nestInterval;
ctx.isRTL = localization.direction === "rtl";
ctx.maxLevels = params.maxLevels || 0;
},
// Set the current group and create the placeholder row that will take the
@@ -164,10 +169,38 @@ export const useNestedSortable = makeDraggableHook({
group: ctx.currentGroup,
};
},
_getDeepestChildLevel(ctx, node, depth = 0) {
let result = 0;
const childSelector = `${ctx.listTagName} ${ctx.elementSelector}`;
for (const childNode of node.querySelectorAll(childSelector)) {
result = Math.max(this._getDeepestChildLevel(ctx, childNode, depth + 1), result);
}
return depth ? result + 1 : result;
},
_hasReachMaxAllowedLevel(ctx) {
if (!ctx.nest || ctx.maxLevels < 1) {
return false;
}
let level = this._getDeepestChildLevel(ctx, ctx.current.element);
let list = ctx.current.placeHolder.closest(ctx.listTagName);
while (list) {
level++;
list = list.parentNode.closest(ctx.listTagName);
}
return level > ctx.maxLevels;
},
_isAllowedNodeMove(ctx) {
return !this._hasReachMaxAllowedLevel(ctx);
},
// Check if the cursor moved enough to trigger a move. If it did, move the
// placeholder accordingly.
onDrag({ ctx, callHandler }) {
const onMove = (prevPos) => {
if (!this._isAllowedNodeMove(ctx)) {
ctx.current.placeHolder.classList.add("d-none");
return;
}
ctx.current.placeHolder.classList.remove("d-none");
callHandler("onMove", {
element: ctx.current.element,
previous: ctx.current.placeHolder.previousElementSibling,
@@ -351,6 +384,9 @@ export const useNestedSortable = makeDraggableHook({
// If the drop position is different from the starting position, run the
// onDrop handler from the parameters.
onDrop({ ctx }) {
if (!this._isAllowedNodeMove(ctx)) {
return;
}
const previous = ctx.current.placeHolder.previousElementSibling;
const next = ctx.current.placeHolder.nextElementSibling;
if (previous !== ctx.current.element && next !== ctx.current.element) {
@@ -924,12 +924,68 @@ QUnit.module("Draggable", ({ beforeEach }) => {
});
assert.verifySteps(
["Initiation of the drag sequence"],
"A drag sequence should have been initiated",
"A drag sequence should have been initiated"
);
await drop();
},
}
);
QUnit.test("shouldn't drag above max level", async (assert) => {
assert.expect(6);
class NestedSortable extends Component {
static template = xml`
<div t-ref="root" class="root">
<ul class="list">
<li class="item" id="parent">
<span>parent</span>
</li>
<li class="item" id="dragged">
<span>dragged</span>
<ul>
<li class="item" id="child">
<span>child</span>
</li>
</ul>
</li>
</ul>
</div>
`;
setup() {
useNestedSortable({
ref: useRef("root"),
elements: ".item",
nest: true,
maxLevels: 2,
onDragStart() {
assert.step("start");
},
onMove() {
assert.step("move");
},
onDrop() {
assert.step("drop");
},
onDragEnd({ element }) {
assert.step("end");
assert.strictEqual(element.id, "dragged");
assert.notOk(element.parentElement.closest("#parent"));
assert.containsOnce(target, ".o_nested_sortable_placeholder.d-none");
},
});
}
}
await mount(NestedSortable, target);
// cant move draggable under parent
const draggedNode = target.querySelector(".item#dragged");
const { drop, moveTo } = await drag(draggedNode);
await moveTo("#parent", "right");
await drop();
assert.verifySteps(["start", "end"]);
});
QUnit.test("Ignore specified elements", async (assert) => {
assert.expect(6);