[IMP] core/utils: make separate useDraggable hook in web

A new useDraggable hook is introduced and used in the various components that
use the feature.

It relies on the draggable hook builder which is a generic way to prepare
specific drag and drop features.

X-original-commit: cb1cfe5f5efe1b683d7fdffde3718bd8bf1bcd86
Part-of: odoo/odoo#102153
This commit is contained in:
Julien Mougenot
2022-10-04 20:58:00 +02:00
committed by Lucas Perais
parent dc7ac937b0
commit 5805ca28a8
6 changed files with 126 additions and 22 deletions
@@ -0,0 +1,62 @@
/** @odoo-module **/
import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
/**
* @typedef DraggableParams
*
* MANDATORY
*
* @property {{ el: HTMLElement | null }} ref
* @property {string} elements defines draggable elements
*
* OPTIONAL
*
* @property {boolean | () => boolean} [enable] whether the draggable system should
* be enabled.
* @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 targetting elements that must
* initiate a drag.
* @property {string | () => string} [cursor] cursor style during the dragging sequence.
*
* HANDLERS (also optional)
*
* @property {(DraggableHandlerParams) => any} [onDragStart]
* called when a dragging sequence is initiated.
* @property {(DraggableHandlerParams) => any} [onDrag]
* called on each "mousemove" during the drag sequence.
* @property {(DraggableHandlerParams) => any} [onDragEnd]
* called when the dragging sequence ends, regardless of the reason.
* @property {(DraggableHandlerParams) => any} [onDrop] called when the dragging sequence
* ends on a mouseup action.
*/
/**
* @typedef DraggableHandlerParams
* @property {number} x current mouse position on the X axis
* @property {number} y current mouse position on the Y axis
* @property {HTMLElement} element
*/
/**
* @typedef DraggableState
* @property {boolean} dragging
*/
/** @type {(params: DraggableParams) => DraggableState} */
export const useDraggable = makeDraggableHook({
name: "useDraggable",
onDragStart({ ctx, helpers }) {
helpers.execHandler("onDragStart", { element: ctx.currentElement });
},
onDrag({ ctx, helpers }) {
helpers.execHandler("onDrag", { element: ctx.currentElement });
},
onDragEnd({ ctx, helpers }) {
helpers.execHandler("onDragEnd", { element: ctx.currentElement });
},
onDrop({ ctx, helpers }) {
helpers.execHandler("onDrop", { element: ctx.currentElement });
},
});
@@ -9,6 +9,26 @@ import { debounce } from "@web/core/utils/timing";
* @property {number} y
*/
/**
* @typedef DraggableBuilderParams
*
* Hook params
* @property {string} [name="useAnonymousDraggable"]
* @property {Record<string, string[]>} [acceptedParams]
*
* Build handlers
* @property {(params: DraggableBuilderHookParams) => any} onBuildSetup
* @property {(params: DraggableBuilderHookParams) => any} onComputeParams
*
* Runtime handlers
* @property {(params: DraggableBuilderHookParams) => any} onWillStartDrag
* @property {(params: DraggableBuilderHookParams) => any} onDragStart
* @property {(params: DraggableBuilderHookParams) => any} onDrag
* @property {(params: DraggableBuilderHookParams) => any} onDragEnd
* @property {(params: DraggableBuilderHookParams) => any} onDrop
* @property {(params: DraggableBuilderHookParams) => any} onCleanup
*/
/**
* @typedef DraggableHookRunningContext
* @property {{ el: HTMLElement | null }} ref
@@ -25,6 +45,15 @@ import { debounce } from "@web/core/utils/timing";
* @property {Position} [offset={ x: 0, y: 0 }]
*/
/**
* @typedef DraggableBuilderHookParams
* @property {DraggableHookRunningContext} ctx
* @property {Object} helpers
* @property {Function} helpers.addListener
* @property {Function} helpers.addStyle
* @property {Function} helpers.execHandler
*/
const { useEffect, useEnv, useExternalListener, onWillUnmount, reactive } = owl;
const DEFAULT_ACCEPTED_PARAMS = {
@@ -57,6 +86,10 @@ function cssValueToNumber(val) {
return Number(val.slice(0, -2));
}
/**
* @param {DraggableBuilderParams} hookParams
* @returns {(params: Record<any, any>) => { dragging: boolean }}
*/
export function makeDraggableHook(hookParams = {}) {
const hookName = hookParams.name || "useAnonymousDraggable";
const allAcceptedParams = { ...DEFAULT_ACCEPTED_PARAMS, ...hookParams.acceptedParams };
+18 -9
View File
@@ -27,17 +27,17 @@ import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
*
* HANDLERS (also optional)
*
* @property {(group: HTMLElement | null, element: HTMLElement) => any} [onStart]
* @property {({ element: HTMLElement, group: HTMLElement | null }) => any} [onDragStart]
* called when a dragging sequence is initiated.
* @property {(element: HTMLElement) => any} [onElementEnter] called when the cursor
* @property {({ element: HTMLElement }) => any} [onElementEnter] called when the cursor
* enters another sortable element.
* @property {(element: HTMLElement) => any} [onElementLeave] called when the cursor
* @property {({ element: HTMLElement }) => any} [onElementLeave] called when the cursor
* leaves another sortable element.
* @property {(group: HTMLElement) => any} [onGroupEnter] (if a `groups` is specified):
* @property {({ group: HTMLElement }) => any} [onGroupEnter] (if a `groups` is specified):
* will be called when the cursor enters another group element.
* @property {(group: HTMLElement) => any} [onGroupLeave] (if a `groups` is specified):
* @property {({ group: HTMLElement }) => any} [onGroupLeave] (if a `groups` is specified):
* will be called when the cursor leaves another group element.
* @property {(group: HTMLElement | null, element: HTMLElement) => any} [onStop]
* @property {({ element: HTMLElement group: HTMLElement | null }) => any} [onDragEnd]
* called when the dragging sequence ends, regardless of the reason.
* @property {(params: DropParams) => any} [onDrop] called when the dragging sequence
* ends on a mouseup action AND the dragged element has been moved elsewhere. The
@@ -54,6 +54,12 @@ import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
* @property {HTMLElement | null} parent
*/
/**
* @typedef SortableState
* @property {boolean} dragging
*/
/** @type {(params: SortableParams) => SortableState} */
export const useSortable = makeDraggableHook({
name: "useSortable",
acceptedParams: {
@@ -159,11 +165,14 @@ export const useSortable = makeDraggableHook({
// Ghost is initially added right after the current element.
ctx.currentElement.after(ctx.ghostElement);
// Calls "onStart" handler
helpers.execHandler("onStart", { element: ctx.currentElement, group: ctx.currentGroup });
// Calls "onDragStart" handler
helpers.execHandler("onDragStart", {
element: ctx.currentElement,
group: ctx.currentGroup,
});
},
onDragEnd({ ctx, helpers }) {
helpers.execHandler("onStop", { element: ctx.currentElement, group: ctx.currentGroup });
helpers.execHandler("onDragEnd", { element: ctx.currentElement, group: ctx.currentGroup });
},
onDrop({ ctx, helpers }) {
const previous = ctx.ghostElement.previousElementSibling;
@@ -56,13 +56,13 @@ export class KanbanRenderer extends Component {
connectGroups: () => this.canMoveRecords,
cursor: "move",
// Hooks
onStart: (params) => {
onDragStart: (params) => {
const { element, group } = params;
dataRecordId = element.dataset.id;
dataGroupId = group && group.dataset.id;
return this.sortStart(params);
},
onStop: (params) => this.sortStop(params),
onDragEnd: (params) => this.sortStop(params),
onGroupEnter: (params) => this.sortRecordGroupEnter(params),
onGroupLeave: (params) => this.sortRecordGroupLeave(params),
onDrop: (params) => this.sortRecordDrop(dataRecordId, dataGroupId, params),
@@ -75,12 +75,12 @@ export class KanbanRenderer extends Component {
handle: ".o_column_title",
cursor: "move",
// Hooks
onStart: (params) => {
onDragStart: (params) => {
const { element } = params;
dataGroupId = element.dataset.id;
return this.sortStart(params);
},
onStop: (params) => this.sortStop(params),
onDragEnd: (params) => this.sortStop(params),
onDrop: (params) => this.sortGroupDrop(dataGroupId, params),
});
}
@@ -116,12 +116,12 @@ export class ListRenderer extends Component {
handle: ".o_handle_cell",
cursor: "grabbing",
// Hooks
onStart: (params) => {
onDragStart: (params) => {
const { element } = params;
dataRowId = element.dataset.id;
return this.sortStart(params);
},
onStop: (params) => this.sortStop(params),
onDragEnd: (params) => this.sortStop(params),
onDrop: (params) => this.sortDrop(dataRowId, params),
});
@@ -95,7 +95,7 @@ QUnit.module("UI", ({ beforeEach }) => {
useSortable({
ref: useRef("root"),
elements: ".item",
onStart({ element, group }) {
onDragStart({ element, group }) {
assert.step("start");
assert.notOk(group);
assert.strictEqual(element.innerText, "1");
@@ -104,7 +104,7 @@ QUnit.module("UI", ({ beforeEach }) => {
assert.step("elemententer");
assert.strictEqual(element.innerText, "2");
},
onStop({ element, group }) {
onDragEnd({ element, group }) {
assert.step("stop");
assert.notOk(group);
assert.strictEqual(element.innerText, "1");
@@ -151,7 +151,7 @@ QUnit.module("UI", ({ beforeEach }) => {
elements: ".item",
groups: ".list",
connectGroups: true,
onStart({ element, group }) {
onDragStart({ element, group }) {
assert.step("start");
assert.hasClass(group, "list2");
assert.strictEqual(element.innerText, "2 1");
@@ -160,7 +160,7 @@ QUnit.module("UI", ({ beforeEach }) => {
assert.step("groupenter");
assert.hasClass(group, "list1");
},
onStop({ element, group }) {
onDragEnd({ element, group }) {
assert.step("stop");
assert.hasClass(group, "list2");
assert.strictEqual(element.innerText, "2 1");
@@ -209,7 +209,7 @@ QUnit.module("UI", ({ beforeEach }) => {
ref: useRef("root"),
elements: ".item",
enable: () => this.state.enableSortable,
onStart() {
onDragStart() {
assert.step("start");
},
});
@@ -251,7 +251,7 @@ QUnit.module("UI", ({ beforeEach }) => {
useSortable({
ref: useRef("root"),
elements: ".item",
onStart() {
onDragStart() {
throw new Error("Shouldn't start the sortable feature.");
},
});
@@ -284,7 +284,7 @@ QUnit.module("UI", ({ beforeEach }) => {
ref: useRef("root"),
elements: ".item",
ignore: ".ignored",
onStart() {
onDragStart() {
assert.step("drag");
},
});