import { setDragStart, getBeforeDragDist, getTransformDist, convertTransformFormat, resolveTransformEvent, fillTransformStartEvent, setDefaultTransformIndex, fillOriginalTransform, } from "../gesto/GestoUtils"; import { triggerEvent, fillParams, getDistSize, prefix, fillEndParams, fillCSSObject, } from "../utils"; import { minus, plus } from "@scena/matrix"; import { DraggableProps, OnDrag, OnDragGroup, OnDragGroupStart, OnDragStart, OnDragEnd, DraggableState, Renderer, OnDragGroupEnd, MoveableManagerInterface, MoveableGroupInterface, } from "../types"; import { triggerChildGesto } from "../groupUtils"; import { startCheckSnapDrag } from "./Snappable"; import { getRad, throttle, throttleArray } from "@daybrush/utils"; import { checkSnapBoundsDrag } from "./snappable/snapBounds"; import { TINY_NUM } from "../consts"; /** * @namespace Draggable * @memberof Moveable * @description Draggable refers to the ability to drag and move targets. */ export default { name: "draggable", props: [ "draggable", "throttleDrag", "throttleDragRotate", "hideThrottleDragRotateLine", "startDragRotate", "edgeDraggable", ] as const, events: [ "dragStart", "drag", "dragEnd", "dragGroupStart", "dragGroup", "dragGroupEnd", ] as const, requestStyle(): string[] { return ["left", "top", "right", "bottom"]; }, requestChildStyle(): string[] { return ["left", "top", "right", "bottom"]; }, render( moveable: MoveableManagerInterface, React: Renderer, ): any[] { const { hideThrottleDragRotateLine, throttleDragRotate, zoom } = moveable.props; const { dragInfo, beforeOrigin } = moveable.getState(); if (hideThrottleDragRotateLine || !throttleDragRotate || !dragInfo) { return []; } const dist = dragInfo.dist; if (!dist[0] && !dist[1]) { return []; } const width = getDistSize(dist); const rad = getRad(dist, [0, 0]); return [
]; }, dragStart( moveable: MoveableManagerInterface, e: any, ) { const { datas, parentEvent, parentGesto } = e; const state = moveable.state; const { gestos, style, } = state; if (gestos.draggable) { return false; } gestos.draggable = parentGesto || moveable.targetGesto; datas.datas = {}; datas.left = parseFloat(style.left || "") || 0; datas.top = parseFloat(style.top || "") || 0; datas.bottom = parseFloat(style.bottom || "") || 0; datas.right = parseFloat(style.right || "") || 0; datas.startValue = [0, 0]; setDragStart(moveable, e); setDefaultTransformIndex(moveable, e, "translate"); startCheckSnapDrag(moveable, datas); datas.prevDist = [0, 0]; datas.prevBeforeDist = [0, 0]; datas.isDrag = false; datas.deltaOffset = [0, 0]; const params = fillParams(moveable, e, { set: (translate: number[]) => { datas.startValue = translate; }, ...fillTransformStartEvent(moveable, e), }); const result = parentEvent || triggerEvent(moveable, "onDragStart", params); if (result !== false) { datas.isDrag = true; moveable.state.dragInfo = { startRect: moveable.getRect(), dist: [0, 0], }; } else { gestos.draggable = null; datas.isPinch = false; } return datas.isDrag ? params : false; }, drag( moveable: MoveableManagerInterface, e: any, ): OnDrag | undefined { if (!e) { return; } resolveTransformEvent(moveable, e, "translate"); const { datas, parentEvent, parentFlag, isPinch, deltaOffset, useSnap, isRequest, isGroup, parentThrottleDrag, } = e; let { distX, distY } = e; const { isDrag, prevDist, prevBeforeDist, startValue } = datas; if (!isDrag) { return; } if (deltaOffset) { distX += deltaOffset[0]; distY += deltaOffset[1]; } const props = moveable.props; const parentMoveable = props.parentMoveable; const throttleDrag = isGroup ? 0 : (props.throttleDrag || parentThrottleDrag || 0); const throttleDragRotate = parentEvent ? 0 : (props.throttleDragRotate || 0); let dragRotateRad = 0; let isVerticalSnap = false; let isVerticalBound = false; let isHorizontalSnap = false; let isHorizontalBound = false; if (!parentEvent && throttleDragRotate > 0 && (distX || distY)) { const startDragRotate = props.startDragRotate || 0; const deg = throttle(startDragRotate + getRad([0, 0], [distX, distY]) * 180 / Math.PI, throttleDragRotate) - startDragRotate; const ry = distY * Math.abs(Math.cos((deg - 90) / 180 * Math.PI)); const rx = distX * Math.abs(Math.cos(deg / 180 * Math.PI)); const r = getDistSize([rx, ry]); dragRotateRad = deg * Math.PI / 180; distX = r * Math.cos(dragRotateRad); distY = r * Math.sin(dragRotateRad); } if (!isPinch && !parentEvent && !parentFlag) { const [verticalInfo, horizontalInfo] = checkSnapBoundsDrag( moveable, distX, distY, throttleDragRotate, (!useSnap && isRequest) || deltaOffset, datas, ); isVerticalSnap = verticalInfo.isSnap; isVerticalBound = verticalInfo.isBound; isHorizontalSnap = horizontalInfo.isSnap; isHorizontalBound = horizontalInfo.isBound; const verticalOffset = verticalInfo.offset; const horizontalOffset = horizontalInfo.offset; distX += verticalOffset; distY += horizontalOffset; } const beforeTranslate = plus(getBeforeDragDist({ datas, distX, distY }), startValue); const translate = plus(getTransformDist({ datas, distX, distY }), startValue); throttleArray(translate, TINY_NUM); throttleArray(beforeTranslate, TINY_NUM); if (!throttleDragRotate) { if (!isVerticalSnap && !isVerticalBound) { translate[0] = throttle(translate[0], throttleDrag); beforeTranslate[0] = throttle(beforeTranslate[0], throttleDrag); } if (!isHorizontalSnap && !isHorizontalBound) { translate[1] = throttle(translate[1], throttleDrag); beforeTranslate[1] = throttle(beforeTranslate[1], throttleDrag); } } const beforeDist = minus(beforeTranslate, startValue); const dist = minus(translate, startValue); const delta = minus(dist, prevDist); const beforeDelta = minus(beforeDist, prevBeforeDist); datas.prevDist = dist; datas.prevBeforeDist = beforeDist; datas.passDelta = delta; //distX - (datas.passDistX || 0); // datas.passDeltaY = distY - (datas.passDistY || 0); datas.passDist = dist; //distX; // datas.passDistY = distY; const left = datas.left + beforeDist[0]; const top = datas.top + beforeDist[1]; const right = datas.right - beforeDist[0]; const bottom = datas.bottom - beforeDist[1]; const nextTransform = convertTransformFormat(datas, `translate(${translate[0]}px, ${translate[1]}px)`, `translate(${dist[0]}px, ${dist[1]}px)`); fillOriginalTransform(e, nextTransform); moveable.state.dragInfo.dist = parentEvent ? [0, 0] : dist; if (!parentEvent && !parentMoveable && delta.every(num => !num) && beforeDelta.some(num => !num)) { return; } const { width, height, } = moveable.state; const params = fillParams(moveable, e, { transform: nextTransform, dist, delta, translate, beforeDist, beforeDelta, beforeTranslate, left, top, right, bottom, width, height, isPinch, ...fillCSSObject({ transform: nextTransform, }, e), }); !parentEvent && triggerEvent(moveable, "onDrag", params); return params; }, dragAfter( moveable: MoveableManagerInterface, e: any, ) { const datas = e.datas; const { deltaOffset, } = datas; if (deltaOffset[0] || deltaOffset[1]) { datas.deltaOffset = [0, 0]; return this.drag(moveable, {...e, deltaOffset }); } return false; }, dragEnd( moveable: MoveableManagerInterface, e: any, ) { const { parentEvent, datas } = e; moveable.state.dragInfo = null; if (!datas.isDrag) { return; } datas.isDrag = false; const param = fillEndParams(moveable, e, {}); !parentEvent && triggerEvent(moveable, "onDragEnd", param); return param; }, dragGroupStart(moveable: MoveableGroupInterface, e: any) { const { datas, clientX, clientY } = e; const params = this.dragStart(moveable, e); if (!params) { return false; } const { childEvents, eventParams, } = triggerChildGesto(moveable, this, "dragStart", [ clientX || 0, clientY || 0, ], e, false, "draggable"); const nextParams: OnDragGroupStart = { ...params, targets: moveable.props.targets!, events: eventParams, }; const result = triggerEvent(moveable, "onDragGroupStart", nextParams); datas.isDrag = result !== false; // find data.startValue and based on first child moveable const startValue = childEvents[0]?.datas.startValue ?? [0, 0]; datas.throttleOffset = [startValue[0] % 1, startValue[1] % 1]; return datas.isDrag ? params : false; }, dragGroup(moveable: MoveableGroupInterface, e: any) { const { datas } = e; if (!datas.isDrag) { return; } const params = this.drag(moveable, { ...e, parentThrottleDrag: moveable.props.throttleDrag, }); const { passDelta } = e.datas; const { eventParams, } = triggerChildGesto(moveable, this, "drag", passDelta, e, false, "draggable"); if (!params) { return; } const nextParams: OnDragGroup = { targets: moveable.props.targets!, events: eventParams, ...params, }; triggerEvent(moveable, "onDragGroup", nextParams); return nextParams; }, dragGroupEnd(moveable: MoveableGroupInterface, e: any) { const { isDrag, datas } = e; if (!datas.isDrag) { return; } this.dragEnd(moveable, e); const { eventParams, } = triggerChildGesto(moveable, this, "dragEnd", [0, 0], e, false, "draggable"); triggerEvent(moveable, "onDragGroupEnd", fillEndParams(moveable, e, { targets: moveable.props.targets!, events: eventParams, })); return isDrag; }, /** * @method Moveable.Draggable#request * @param {object} [e] - the draggable's request parameter * @param {number} [e.x] - x position * @param {number} [e.y] - y position * @param {number} [e.deltaX] - X number to move * @param {number} [e.deltaY] - Y number to move * @return {Moveable.Requester} Moveable Requester * @example * // Instantly Request (requestStart - request - requestEnd) * // Use Relative Value * moveable.request("draggable", { deltaX: 10, deltaY: 10 }, true); * // Use Absolute Value * moveable.request("draggable", { x: 200, y: 100 }, true); * * // requestStart * const requester = moveable.request("draggable"); * * // request * // Use Relative Value * requester.request({ deltaX: 10, deltaY: 10 }); * requester.request({ deltaX: 10, deltaY: 10 }); * requester.request({ deltaX: 10, deltaY: 10 }); * // Use Absolute Value * moveable.request("draggable", { x: 200, y: 100 }); * moveable.request("draggable", { x: 220, y: 100 }); * moveable.request("draggable", { x: 240, y: 100 }); * * // requestEnd * requester.requestEnd(); */ request(moveable: MoveableManagerInterface) { const datas = {}; const rect = moveable.getRect(); let distX = 0; let distY = 0; let useSnap = false; return { isControl: false, requestStart(e: Record) { useSnap = e.useSnap; return { datas, useSnap }; }, request(e: Record) { if ("x" in e) { distX = e.x - rect.left; } else if ("deltaX" in e) { distX += e.deltaX; } if ("y" in e) { distY = e.y - rect.top; } else if ("deltaY" in e) { distY += e.deltaY; } return { datas, distX, distY, useSnap }; }, requestEnd() { return { datas, isDrag: true, useSnap }; }, }; }, unset(moveable: MoveableManagerInterface>) { moveable.state.gestos.draggable = null; moveable.state.dragInfo = null; }, }; /** * Whether or not target can be dragged. (default: false) * @name Moveable.Draggable#draggable * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body); * * moveable.draggable = true; */ /** * throttle of x, y when drag. * @name Moveable.Draggable#throttleDrag * @default 0 * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body); * * moveable.throttleDrag = 1; */ /** * throttle of angle of x, y when drag. * @name Moveable.Draggable#throttleDragRotate * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body); * * moveable.throttleDragRotate = 45; */ /** * start angle of throttleDragRotate of x, y when drag. * @name Moveable.Draggable#startDragRotate * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body); * * // 45, 135, 225, 315 * moveable.throttleDragRotate = 90; * moveable.startDragRotate = 45; */ /** * When the drag starts, the dragStart event is called. * @memberof Moveable.Draggable * @event dragStart * @param {Moveable.Draggable.OnDragStart} - Parameters for the dragStart event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { draggable: true }); * moveable.on("dragStart", ({ target }) => { * console.log(target); * }); */ /** * When dragging, the drag event is called. * @memberof Moveable.Draggable * @event drag * @param {Moveable.Draggable.OnDrag} - Parameters for the drag event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { draggable: true }); * moveable.on("drag", ({ target, transform }) => { * target.style.transform = transform; * }); */ /** * When the drag finishes, the dragEnd event is called. * @memberof Moveable.Draggable * @event dragEnd * @param {Moveable.Draggable.OnDragEnd} - Parameters for the dragEnd event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { draggable: true }); * moveable.on("dragEnd", ({ target, isDrag }) => { * console.log(target, isDrag); * }); */ /** * When the group drag starts, the `dragGroupStart` event is called. * @memberof Moveable.Draggable * @event dragGroupStart * @param {Moveable.Draggable.OnDragGroupStart} - Parameters for the `dragGroupStart` event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * target: [].slice.call(document.querySelectorAll(".target")), * draggable: true * }); * moveable.on("dragGroupStart", ({ targets }) => { * console.log("onDragGroupStart", targets); * }); */ /** * When the group drag, the `dragGroup` event is called. * @memberof Moveable.Draggable * @event dragGroup * @param {Moveable.Draggable.OnDragGroup} - Parameters for the `dragGroup` event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * target: [].slice.call(document.querySelectorAll(".target")), * draggable: true * }); * moveable.on("dragGroup", ({ targets, events }) => { * console.log("onDragGroup", targets); * events.forEach(ev => { * // drag event * console.log("onDrag left, top", ev.left, ev.top); * // ev.target!.style.left = `${ev.left}px`; * // ev.target!.style.top = `${ev.top}px`; * console.log("onDrag translate", ev.dist); * ev.target!.style.transform = ev.transform;) * }); * }); */ /** * When the group drag finishes, the `dragGroupEnd` event is called. * @memberof Moveable.Draggable * @event dragGroupEnd * @param {Moveable.Draggable.OnDragGroupEnd} - Parameters for the `dragGroupEnd` event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * target: [].slice.call(document.querySelectorAll(".target")), * draggable: true * }); * moveable.on("dragGroupEnd", ({ targets, isDrag }) => { * console.log("onDragGroupEnd", targets, isDrag); * }); */