[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:
committed by
Lucas Perais
parent
dc7ac937b0
commit
5805ca28a8
@@ -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 };
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user