[IMP] web: draggable_hook_builder option to ignore selectively

The way the `ignore` property is implemented means that nested draggable
elements under a single element matching the `ignore` selector will all be
disabled.

In Knowledge, the sidebar allowing to move articles around has nested draggable
elements that the user should be able to move around, under other draggable
elements that should stay in place (depending on access rights).

Therefore, this commit introduces another way to disable dragging with the
`preventDrag` property which is a function that receives the dragging candidate
and returns a boolean to indicate whether the element can effectively be dragged
or not in its current situation.

task-3456741

closes odoo/odoo#135981

X-original-commit: eb302dbf48500b2eee61a0efb865868866fb454b
Related: odoo/enterprise#47640
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Signed-off-by: Damien Abeloos (abd) <abd@odoo.com>
This commit is contained in:
abd-msyukyu-odoo
2023-09-21 06:31:01 +00:00
parent eeebe32bd0
commit 657314adb1
3 changed files with 83 additions and 3 deletions
@@ -34,6 +34,7 @@ import { hasTouch, isBrowserFirefox, isIOS } from "../browser/feature_detection"
* @property {boolean} [followCursor=true]
* @property {string | null} [cursor=null]
* @property {() => boolean} [enable=() => false]
* @property {(HTMLElement) => boolean} [preventDrag=(el) => false]
* @property {Position} [pointer={ x: 0, y: 0 }]
* @property {EdgeScrollingOptions} [edgeScrolling]
* @property {number} [delay]
@@ -77,6 +78,7 @@ const DRAGGED_CLASS = "o_dragged";
const DEFAULT_ACCEPTED_PARAMS = {
enable: [Boolean, Function],
preventDrag: [Function],
ref: [Object],
elements: [String],
handle: [String, Function],
@@ -89,6 +91,7 @@ const DEFAULT_ACCEPTED_PARAMS = {
const DEFAULT_DEFAULT_PARAMS = {
elements: `.${DRAGGABLE_CLASS}`,
enable: true,
preventDrag: () => false,
edgeScrolling: {
speed: 10,
threshold: 30,
@@ -658,11 +661,13 @@ export function makeDraggableHook(hookParams) {
// outside of the window.
dragEnd(null);
const fullSelectorEl = ev.target.closest(ctx.fullSelector);
if (
ev.button !== LEFT_CLICK ||
!ctx.enable() ||
!ev.target.closest(ctx.fullSelector) ||
(ctx.ignoreSelector && ev.target.closest(ctx.ignoreSelector))
!fullSelectorEl ||
(ctx.ignoreSelector && ev.target.closest(ctx.ignoreSelector)) ||
ctx.preventDrag(fullSelectorEl)
) {
return;
}
@@ -886,6 +891,7 @@ export function makeDraggableHook(hookParams) {
/** @type {DraggableHookContext} */
const ctx = {
enable: () => false,
preventDrag: () => false,
ref: params.ref,
ignoreSelector: null,
fullSelector: null,
@@ -911,6 +917,11 @@ export function makeDraggableHook(hookParams) {
// Enable getter
ctx.enable = actualParams.enable;
// Dragging constraint
if (actualParams.preventDrag) {
ctx.preventDrag = actualParams.preventDrag;
}
// Selectors
ctx.elementSelector = actualParams.elements;
if (!ctx.elementSelector) {
@@ -22,7 +22,11 @@ import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
* @property {string | () => string} [handle] additional selector for when the dragging
* sequence must be initiated when dragging on a certain part of the element.
* @property {string | () => string} [ignore] selector targeting elements that must
* initiate a drag.
* initiate a drag (recursive in depth: all draggable elements under an
* ignored element will be locked in place)
* @property {(HTMLElement) => boolean} [preventDrag] function receiving a
* the current target for dragging (element) and returning a boolean, whether
* the element can be effectively dragged or not.
* @property {boolean | () => boolean} [connectGroups] whether elements can be dragged
* across different parent groups. Note that it requires a `groups` param to work.
* @property {string | () => string} [cursor] cursor style during the dragging sequence.
@@ -215,6 +215,71 @@ QUnit.module("Draggable", ({ beforeEach }) => {
assert.verifySteps([]);
});
QUnit.test("Ignore specific elements in a nested draggable", async (assert) => {
assert.expect(7);
class List extends Component {
static components = { List };
static template = xml`
<div t-ref="root" class="root">
<ul class="list">
<li t-foreach="[0, 1]" t-as="i" t-key="i"
t-attf-class="item parent #{ i % 2 ? 'ignored' : 'not-ignored' }">
<span t-esc="'parent' + i" />
<ul class="list">
<li t-foreach="[0, 1]" t-as="j" t-key="j"
t-attf-class="item child #{ j % 2 ? 'ignored' : 'not-ignored' }">
<span t-esc="'child' + j" />
</li>
</ul>
</li>
</ul>
</div>`;
setup() {
useDraggable({
ref: useRef("root"),
elements: ".item",
preventDrag: (el) => el.classList.contains('ignored'),
onDragStart() {
assert.step("drag");
},
});
}
}
await mount(List, target);
assert.verifySteps([]);
// Drag ignored under non-ignored -> block
await dragAndDrop(
".not-ignored.parent .ignored.child",
".not-ignored.parent .not-ignored.child"
);
assert.verifySteps([]);
// Drag not-ignored-under not-ignored -> succeed
await dragAndDrop(
".not-ignored.parent .not-ignored.child",
".not-ignored.parent .ignored.child"
);
assert.verifySteps(["drag"]);
// Drag ignored under ignored -> block
await dragAndDrop(
".ignored.parent .ignored.child",
".ignored.parent .not-ignored.child"
);
assert.verifySteps([]);
// Drag not-ignored under ignored -> succeed
await dragAndDrop(
".ignored.parent .not-ignored.child",
".ignored.parent .ignored.child"
);
assert.verifySteps(["drag"]);
});
QUnit.test("Dragging element with touch event", async (assert) => {
assert.expect(10);