import { prefix, triggerEvent, fillParams, calculatePosition, fillEndParams, getRotationRad, getRefTargets, catchEvent, getProps, calculateMoveableClientPositions, fillAfterTransform, getTotalOrigin, } from "../utils"; import { IObject, hasClass, getRad, throttle, getDist, getKeys, isArray, } from "@daybrush/utils"; import { RotatableProps, OnRotateGroup, OnRotateGroupEnd, Renderer, OnRotateGroupStart, OnRotateStart, OnRotate, OnRotateEnd, MoveableClientRect, SnappableProps, SnappableState, MoveableManagerInterface, MoveableGroupInterface, DraggableProps, OnBeforeRotate, OnBeforeRotateGroup, OnResizeStart, OnResize, TransformObject, OnDragStart, } from "../types"; import { triggerChildAbles } from "../groupUtils"; import { calculate, convertPositionMatrix, getOrigin, minus, plus, rotate as rotateMatrix } from "@scena/matrix"; import CustomGesto, { setCustomDrag } from "../gesto/CustomGesto"; import { checkSnapRotate } from "./Snappable"; import { fillTransformStartEvent, convertTransformFormat, getRotateDist, fillTransformEvent, setDefaultTransformIndex, resolveTransformEvent, getTransformDirection, getPosByDirection, getTranslateFixedPosition, } from "../gesto/GestoUtils"; import { DirectionControlInfo, renderAroundControls, renderDirectionControlsByInfos } from "../renderDirections"; import { DIRECTION_REGION_TO_DIRECTION } from "../consts"; import Resizable from "./Resizable"; import Draggable from "./Draggable"; import { getOffsetFixedDirectionInfo, getOffsetFixedPositionInfo } from "../utils/getFixedDirection"; /** * @namespace Rotatable * @memberof Moveable * @description Rotatable indicates whether the target can be rotated. */ function setRotateStartInfo( moveable: MoveableManagerInterface, datas: IObject, clientX: number, clientY: number, rect: MoveableClientRect, ) { const groupable = moveable.props.groupable; const state = moveable.state; const n = state.is3d ? 4 : 3; const origin = datas.origin; const nextOrigin = calculatePosition( moveable.state.rootMatrix, // TO-DO #710 minus([origin[0], origin[1]], groupable ? [0, 0] : [state.left, state.top]), n, ); const startAbsoluteOrigin = plus([rect.left, rect.top], nextOrigin); datas.startAbsoluteOrigin = startAbsoluteOrigin; datas.prevDeg = getRad(startAbsoluteOrigin, [clientX, clientY]) / Math.PI * 180; datas.defaultDeg = datas.prevDeg; datas.prevSnapDeg = 0; datas.loop = 0; datas.startDist = getDist(startAbsoluteOrigin, [clientX, clientY]); } function getAbsoluteDist( deg: number, direction: number, datas: IObject, ) { const { defaultDeg, prevDeg, } = datas; let normalizedPrevDeg = prevDeg % 360; let loop = Math.floor(prevDeg / 360); if (normalizedPrevDeg < 0) { normalizedPrevDeg += 360; } if (normalizedPrevDeg > deg && normalizedPrevDeg > 270 && deg < 90) { // 360 => 0 ++loop; } else if (normalizedPrevDeg < deg && normalizedPrevDeg < 90 && deg > 270) { // 0 => 360 --loop; } const dist = direction * (loop * 360 + deg - defaultDeg); datas.prevDeg = defaultDeg + dist; return dist; } function getAbsoluteDistByClient( clientX: number, clientY: number, direction: number, datas: IObject, ) { return getAbsoluteDist( getRad(datas.startAbsoluteOrigin, [clientX, clientY]) / Math.PI * 180, direction, datas, ); } function getRotateInfo( moveable: MoveableManagerInterface, moveableRect: any, datas: IObject, dist: number, startValue: number, checkSnap?: boolean, ) { const { throttleRotate = 0, } = moveable.props; const prevSnapDeg = datas.prevSnapDeg; let snapRotation = 0; let isSnap = false; if (checkSnap) { const result = checkSnapRotate( moveable, moveableRect, dist, startValue + dist, ); isSnap = result.isSnap; snapRotation = startValue + result.dist; } if (!isSnap) { snapRotation = throttle(startValue + dist, throttleRotate); } const snapDeg = snapRotation - startValue; datas.prevSnapDeg = snapDeg; return [snapDeg - prevSnapDeg, snapDeg, snapRotation]; } export function getReversePositionX(dir: string) { if (dir === "left") { return "right"; } else if (dir === "right") { return "left"; } return dir; } export function getReversePositionY(dir: string) { if (dir === "top") { return "bottom"; } else if (dir === "bottom") { return "top"; } return dir; } export function getRotationPositions( rotationPosition: RotatableProps["rotationPosition"], [pos1, pos2, pos3, pos4]: number[][], direction: number, ): [number[], number][] { if (rotationPosition === "none") { return []; } if (isArray(rotationPosition)) { return rotationPosition.map(child => getRotationPositions( child, [pos1, pos2, pos3, pos4], direction, )[0]); } const [dir1, dir2] = (rotationPosition || "top").split("-"); let radPoses = [pos1, pos2]; if (dir1 === "left") { radPoses = [pos3, pos1]; } else if (dir1 === "right") { radPoses = [pos2, pos4]; } else if (dir1 === "bottom") { radPoses = [pos4, pos3]; } let pos = [ (radPoses[0][0] + radPoses[1][0]) / 2, (radPoses[0][1] + radPoses[1][1]) / 2, ]; const rad = getRotationRad(radPoses, direction); if (dir2) { const isStart = dir2 === "top" || dir2 === "left"; const isReverse = dir1 === "bottom" || dir1 === "left"; pos = radPoses[(isStart && !isReverse) || (!isStart && isReverse) ? 0 : 1]; } return [[pos, rad]]; } export function dragControlCondition(moveable: MoveableManagerInterface, e: any) { if (e.isRequest) { return e.requestAble === "rotatable"; } const target = e.inputEvent.target as HTMLElement; if ( hasClass(target, prefix("rotation-control")) || (moveable.props.rotateAroundControls && hasClass(target, prefix("around-control"))) || (hasClass(target, prefix("control")) && hasClass(target, prefix("rotatable"))) ) { return true; } const rotationTarget = moveable.props.rotationTarget; if (rotationTarget) { return getRefTargets(rotationTarget, true).some(element => { if (!element) { return false; } return target === element || target.contains(element); }); } return false; } const css = `.rotation { position: absolute; height: 40px; width: 1px; transform-origin: 50% 100%; height: calc(40px * var(--zoom)); top: auto; left: 0; bottom: 100%; will-change: transform; } .rotation .rotation-line { display: block; width: 100%; height: 100%; transform-origin: 50% 50%; } .rotation .rotation-control { border-color: #4af; border-color: var(--moveable-color); background:#fff; cursor: alias; } :global .view-rotation-dragging, .rotatable.direction.control { cursor: alias; } .rotatable.direction.control.move { cursor: move; } `; export default { name: "rotatable", canPinch: true, props: [ "rotatable", "rotationPosition", "throttleRotate", "renderDirections", "rotationTarget", "rotateAroundControls", "edge", "resolveAblesWithRotatable", "displayAroundControls", ] as const, events: [ "rotateStart", "beforeRotate", "rotate", "rotateEnd", "rotateGroupStart", "beforeRotateGroup", "rotateGroup", "rotateGroupEnd", ] as const, css: [css], viewClassName(moveable: MoveableManagerInterface) { if (!moveable.isDragging("rotatable")) { return ""; } return prefix("view-rotation-dragging"); }, render(moveable: MoveableManagerInterface, React: Renderer): any { const { rotatable, rotationPosition, zoom, renderDirections, rotateAroundControls, resolveAblesWithRotatable, } = getProps(moveable.props, "rotatable"); const { renderPoses, direction, } = moveable.getState(); if (!rotatable) { return null; } const positions = getRotationPositions(rotationPosition!, renderPoses, direction); const jsxs: any[] = []; positions.forEach(([pos, rad], i) => { jsxs.push(
); }); if (renderDirections) { const ables = getKeys(resolveAblesWithRotatable || {}); const resolveMap: Record = {}; ables.forEach(name => { resolveAblesWithRotatable![name]!.forEach(direction => { resolveMap[direction] = name; }); }); let directionControlInfos: DirectionControlInfo[] = []; if (isArray(renderDirections)) { directionControlInfos = renderDirections.map(dir => { const able = resolveMap[dir]; return { data: able ? { resolve: able } : {}, classNames: able ? [`move`] : [], dir, }; }); } jsxs.push(...renderDirectionControlsByInfos( moveable, "rotatable", directionControlInfos, React, )); } if (rotateAroundControls) { jsxs.push(...renderAroundControls(moveable, React)); } return jsxs; }, dragControlCondition: dragControlCondition as (moveable: any, e: any) => boolean, dragControlStart( moveable: MoveableManagerInterface, e: any) { const { datas, clientX, clientY, parentRotate, parentFlag, isPinch, isRequest, } = e; const state = moveable.state; const { target, left, top, direction, beforeDirection, targetTransform, moveableClientRect, offsetMatrix, targetMatrix, allMatrix, width, height, } = state; if (!isRequest && !target) { return false; } const rect = moveable.getRect(); datas.rect = rect; datas.transform = targetTransform; datas.left = left; datas.top = top; let setFixedPosition = (fixedPosition: number[]) => { const result = getOffsetFixedPositionInfo(moveable.state, fixedPosition); datas.fixedDirection = result.fixedDirection; datas.fixedOffset = result.fixedOffset; datas.fixedPosition = result.fixedPosition; if (resizeStart) { resizeStart.setFixedPosition(fixedPosition); } }; let setFixedDirection: OnRotateStart["setFixedDirection"] = (fixedDirection: number[]) => { const result = getOffsetFixedDirectionInfo(moveable.state, fixedDirection); datas.fixedDirection = result.fixedDirection; datas.fixedOffset = result.fixedOffset; datas.fixedPosition = result.fixedPosition; if (resizeStart) { resizeStart.setFixedDirection(fixedDirection); } }; let startClientX = clientX; let startClientY = clientY; if (isRequest || isPinch || parentFlag) { const externalRotate = parentRotate || 0; datas.beforeInfo = { origin: rect.beforeOrigin, prevDeg: externalRotate, defaultDeg: externalRotate, prevSnapDeg: 0, startDist: 0, }; datas.afterInfo = { ...datas.beforeInfo, origin: rect.origin, }; datas.absoluteInfo = { ...datas.beforeInfo, origin: rect.origin, startValue: externalRotate, }; } else { const inputTarget = e.inputEvent?.target; if (inputTarget) { const regionDirection = inputTarget.getAttribute("data-direction") || ""; const controlDirection = DIRECTION_REGION_TO_DIRECTION[regionDirection]; if (controlDirection) { datas.isControl = true; datas.isAroundControl = hasClass(inputTarget, prefix("around-control")); datas.controlDirection = controlDirection; const resolve = inputTarget.getAttribute("data-resolve"); if (resolve) { datas.resolveAble = resolve; } const clientPoses = calculateMoveableClientPositions( state.rootMatrix, state.renderPoses, moveableClientRect, ); [startClientX, startClientY] = getPosByDirection(clientPoses, controlDirection); } } datas.beforeInfo = { origin: rect.beforeOrigin }; datas.afterInfo = { origin: rect.origin }; datas.absoluteInfo = { origin: rect.origin, startValue: rect.rotation, }; const originalFixedPosition = setFixedPosition; setFixedPosition = (fixedPosition: number[]) => { const n = state.is3d ? 4 : 3; const [originX, originY] = plus(getOrigin(targetMatrix, n), fixedPosition); const fixedBeforeOrigin = calculate( offsetMatrix, convertPositionMatrix([originX, originY], n), ); const fixedAfterOrigin = calculate( allMatrix, convertPositionMatrix([fixedPosition[0], fixedPosition[1]], n), ); originalFixedPosition(fixedPosition); const posDelta = state.posDelta; datas.beforeInfo.origin = minus(fixedBeforeOrigin, posDelta); datas.afterInfo.origin = minus(fixedAfterOrigin, posDelta); datas.absoluteInfo.origin = minus(fixedAfterOrigin, posDelta); setRotateStartInfo(moveable, datas.beforeInfo, startClientX, startClientY, moveableClientRect); setRotateStartInfo(moveable, datas.afterInfo, startClientX, startClientY, moveableClientRect); setRotateStartInfo(moveable, datas.absoluteInfo, startClientX, startClientY, moveableClientRect); }; setFixedDirection = (fixedDirection: number[]) => { const fixedPosition = getPosByDirection([ [0, 0], [width, 0], [0, height], [width, height], ], fixedDirection); setFixedPosition(fixedPosition); }; } datas.startClientX = startClientX; datas.startClientY = startClientY; datas.direction = direction; datas.beforeDirection = beforeDirection; datas.startValue = 0; datas.datas = {}; setDefaultTransformIndex(moveable, e, "rotate"); let dragStart: OnDragStart | false = false; let resizeStart: OnResizeStart | false = false; if (datas.isControl && datas.resolveAble) { const resolveAble = datas.resolveAble; if (resolveAble === "resizable") { resizeStart = Resizable.dragControlStart(moveable, { ...(new CustomGesto("resizable").dragStart([0, 0], e)), parentPosition: datas.controlPosition, parentFixedPosition: datas.fixedPosition, }); } } if (!resizeStart) { dragStart = Draggable.dragStart!( moveable, new CustomGesto().dragStart([0, 0], e), ); } setFixedPosition(getTotalOrigin(moveable)); const params = fillParams(moveable, e, { set: (rotatation: number) => { datas.startValue = rotatation * Math.PI / 180; }, setFixedDirection, setFixedPosition, ...fillTransformStartEvent(moveable, e), dragStart, resizeStart, }); const result = triggerEvent(moveable, "onRotateStart", params); datas.isRotate = result !== false; state.snapRenderInfo = { request: e.isRequest, }; return datas.isRotate ? params : false; }, dragControl( moveable: MoveableManagerInterface, e: any, ) { const { datas, clientDistX, clientDistY, parentRotate, parentFlag, isPinch, groupDelta, resolveMatrix, } = e; const { beforeDirection, beforeInfo, afterInfo, absoluteInfo, isRotate, startValue, rect, startClientX, startClientY, } = datas; if (!isRotate) { return; } resolveTransformEvent(moveable, e, "rotate"); const targetDirection = getTransformDirection(e); const direction = beforeDirection * targetDirection; const { parentMoveable, } = moveable.props; let beforeDelta = 0; let beforeDist: number; let beforeRotation: number; let delta = 0; let dist: number; let rotation: number; let absoluteDelta = 0; let absoluteDist: number; let absoluteRotation: number; const startRotation = 180 / Math.PI * startValue; const absoluteStartRotation = absoluteInfo.startValue; let isSnap = false; const nextClientX = startClientX + clientDistX; const nextClientY = startClientY + clientDistY; if (!parentFlag && "parentDist" in e) { const parentDist = e.parentDist; beforeDist = parentDist; dist = parentDist; absoluteDist = parentDist; } else if (isPinch || parentFlag) { beforeDist = getAbsoluteDist(parentRotate, beforeDirection, beforeInfo); dist = getAbsoluteDist(parentRotate, direction, afterInfo); absoluteDist = getAbsoluteDist(parentRotate, direction, absoluteInfo); } else { beforeDist = getAbsoluteDistByClient(nextClientX, nextClientY, beforeDirection, beforeInfo); dist = getAbsoluteDistByClient(nextClientX, nextClientY, direction, afterInfo); absoluteDist = getAbsoluteDistByClient(nextClientX, nextClientY, direction, absoluteInfo); isSnap = true; } beforeRotation = startRotation + beforeDist; rotation = startRotation + dist; absoluteRotation = absoluteStartRotation + absoluteDist; triggerEvent(moveable, "onBeforeRotate", fillParams(moveable, e, { beforeRotation, rotation, absoluteRotation, setRotation(nextRotation: number) { dist = nextRotation - startRotation; beforeDist = dist; absoluteDist = dist; }, }, true)); [ beforeDelta, beforeDist, beforeRotation, ] = getRotateInfo(moveable, rect, beforeInfo, beforeDist, startRotation, isSnap); [ delta, dist, rotation, ] = getRotateInfo(moveable, rect, afterInfo, dist, startRotation, isSnap); [ absoluteDelta, absoluteDist, absoluteRotation, ] = getRotateInfo(moveable, rect, absoluteInfo, absoluteDist, absoluteStartRotation, isSnap); if (!absoluteDelta && !delta && !beforeDelta && !parentMoveable && !resolveMatrix) { return; } const nextTransform = convertTransformFormat( datas, `rotate(${rotation}deg)`, `rotate(${dist}deg)`, ); if (resolveMatrix) { datas.fixedPosition = getTranslateFixedPosition( moveable, datas.targetAllTransform, datas.fixedDirection, datas.fixedOffset, datas, ); } const inverseDist = getRotateDist(moveable, dist, datas); const inverseDelta = minus( plus(groupDelta || [0, 0], inverseDist), datas.prevInverseDist || [0, 0], ); datas.prevInverseDist = inverseDist; datas.requestValue = null; const dragEvent = fillTransformEvent( moveable, nextTransform, inverseDelta, isPinch, e, ); let transformEvent: TransformObject = dragEvent; const parentDistance = getDist( [nextClientX, nextClientY], absoluteInfo.startAbsoluteOrigin, ) - absoluteInfo.startDist; let resize: OnResize | undefined = undefined; if (datas.resolveAble === "resizable") { const resizeEvent = Resizable.dragControl( moveable, { ...setCustomDrag(e, moveable.state, [e.deltaX, e.deltaY], !!isPinch, false, "resizable"), resolveMatrix: true, parentDistance, }, ); if (resizeEvent) { resize = resizeEvent; transformEvent = fillAfterTransform(transformEvent, resizeEvent, e); } } const params = fillParams(moveable, e, { delta, dist, rotate: rotation, rotation, beforeDist, beforeDelta, beforeRotate: beforeRotation, beforeRotation, absoluteDist, absoluteDelta, absoluteRotate: absoluteRotation, absoluteRotation, isPinch: !!isPinch, resize, ...dragEvent, ...transformEvent, }); triggerEvent(moveable, "onRotate", params); return params; }, dragControlEnd(moveable: MoveableManagerInterface, e: any) { const { datas } = e; if (!datas.isRotate) { return; } datas.isRotate = false; const params = fillEndParams(moveable, e, {}); triggerEvent(moveable, "onRotateEnd", params); return params; }, dragGroupControlCondition: dragControlCondition as (moveable: any, e: any) => boolean, dragGroupControlStart(moveable: MoveableGroupInterface, e: any) { const { datas } = e; const { left: parentLeft, top: parentTop, beforeOrigin: parentBeforeOrigin, } = moveable.state; const params = this.dragControlStart(moveable, e); if (!params) { return false; } params.set(datas.beforeDirection * moveable.rotation); const events = triggerChildAbles( moveable, this, "dragControlStart", e, (child, ev) => { const { left, top, beforeOrigin } = child.state; const childClient = plus( minus([left, top], [parentLeft, parentTop]), minus(beforeOrigin, parentBeforeOrigin), ); ev.datas.startGroupClient = childClient; ev.datas.groupClient = childClient; return { ...ev, parentRotate: 0 }; }, ); const nextParams: OnRotateGroupStart = { ...params, targets: moveable.props.targets!, events, }; const result = triggerEvent(moveable, "onRotateGroupStart", nextParams); datas.isRotate = result !== false; return datas.isRotate ? params : false; }, dragGroupControl(moveable: MoveableGroupInterface, e: any) { const { datas } = e; if (!datas.isRotate) { return; } catchEvent(moveable, "onBeforeRotate", parentEvent => { triggerEvent(moveable, "onBeforeRotateGroup", fillParams(moveable, e, { ...parentEvent, targets: moveable.props.targets!, }, true)); }); const params = this.dragControl(moveable, e); if (!params) { return; } const direction = datas.beforeDirection; const parentRotate = params.beforeDist; const rad = parentRotate / 180 * Math.PI; const events = triggerChildAbles( moveable, this, "dragControl", e, (_, ev) => { const startGroupClient = ev.datas.startGroupClient; const [prevClientX, prevClientY] = ev.datas.groupClient; const [clientX, clientY] = rotateMatrix(startGroupClient, rad * direction); const delta = [clientX - prevClientX, clientY - prevClientY]; ev.datas.groupClient = [clientX, clientY]; return { ...ev, parentRotate, groupDelta: delta }; }, ); moveable.rotation = direction * params.beforeRotation; const nextParams: OnRotateGroup = { targets: moveable.props.targets!, events, set(rotation: number) { moveable.rotation = rotation; }, setGroupRotation(rotation: number) { moveable.rotation = rotation; }, ...params, }; triggerEvent(moveable, "onRotateGroup", nextParams); return nextParams; }, dragGroupControlEnd(moveable: MoveableGroupInterface, e: any) { const { isDrag, datas } = e; if (!datas.isRotate) { return; } this.dragControlEnd(moveable, e); const events = triggerChildAbles(moveable, this, "dragControlEnd", e); const nextParams = fillEndParams(moveable, e, { targets: moveable.props.targets!, events, }); triggerEvent(moveable, "onRotateGroupEnd", nextParams); return isDrag; }, /** * @method Moveable.Rotatable#request * @param {object} [e] - the Resizable's request parameter * @param {number} [e.deltaRotate=0] - delta number of rotation * @param {number} [e.rotate=0] - absolute number of moveable's rotation * @return {Moveable.Requester} Moveable Requester * @example * // Instantly Request (requestStart - request - requestEnd) * moveable.request("rotatable", { deltaRotate: 10 }, true); * * * moveable.request("rotatable", { rotate: 10 }, true); * * // requestStart * const requester = moveable.request("rotatable"); * * // request * requester.request({ deltaRotate: 10 }); * requester.request({ deltaRotate: 10 }); * requester.request({ deltaRotate: 10 }); * * requester.request({ rotate: 10 }); * requester.request({ rotate: 20 }); * requester.request({ rotate: 30 }); * * // requestEnd * requester.requestEnd(); */ request(moveable: MoveableManagerInterface) { const datas = {}; let distRotate = 0; const startRotation = moveable.getRotation(); return { isControl: true, requestStart() { return { datas }; }, request(e: IObject) { if ("deltaRotate" in e) { distRotate += e.deltaRotate; } else if ("rotate" in e) { distRotate = e.rotate - startRotation; } return { datas, parentDist: distRotate }; }, requestEnd() { return { datas, isDrag: true }; }, }; }, }; /** * Whether or not target can be rotated. (default: false) * @name Moveable.Rotatable#rotatable * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body); * * moveable.rotatable = true; */ /** * You can specify the position of the rotation. (default: "top") * @name Moveable.Rotatable#rotationPosition * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * rotationPosition: "top", * }); * * moveable.rotationPosition = "bottom" */ /** * throttle of angle(degree) when rotate. * @name Moveable.Rotatable#throttleRotate * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body); * * moveable.throttleRotate = 1; */ /** * When the rotate starts, the rotateStart event is called. * @memberof Moveable.Rotatable * @event rotateStart * @param {Moveable.Rotatable.OnRotateStart} - Parameters for the rotateStart event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { rotatable: true }); * moveable.on("rotateStart", ({ target }) => { * console.log(target); * }); */ /** * When rotating, the rotate event is called. * @memberof Moveable.Rotatable * @event rotate * @param {Moveable.Rotatable.OnRotate} - Parameters for the rotate event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { rotatable: true }); * moveable.on("rotate", ({ target, transform, dist }) => { * target.style.transform = transform; * }); */ /** * When the rotate finishes, the rotateEnd event is called. * @memberof Moveable.Rotatable * @event rotateEnd * @param {Moveable.Rotatable.OnRotateEnd} - Parameters for the rotateEnd event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { rotatable: true }); * moveable.on("rotateEnd", ({ target, isDrag }) => { * console.log(target, isDrag); * }); */ /** * When the group rotate starts, the `rotateGroupStart` event is called. * @memberof Moveable.Rotatable * @event rotateGroupStart * @param {Moveable.Rotatable.OnRotateGroupStart} - Parameters for the `rotateGroupStart` event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * target: [].slice.call(document.querySelectorAll(".target")), * rotatable: true * }); * moveable.on("rotateGroupStart", ({ targets }) => { * console.log("onRotateGroupStart", targets); * }); */ /** * When the group rotate, the `rotateGroup` event is called. * @memberof Moveable.Rotatable * @event rotateGroup * @param {Moveable.Rotatable.OnRotateGroup} - Parameters for the `rotateGroup` event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * target: [].slice.call(document.querySelectorAll(".target")), * rotatable: true * }); * moveable.on("rotateGroup", ({ targets, events }) => { * console.log("onRotateGroup", targets); * events.forEach(ev => { * const target = ev.target; * // ev.drag is a drag event that occurs when the group rotate. * const left = ev.drag.beforeDist[0]; * const top = ev.drag.beforeDist[1]; * const deg = ev.beforeDist; * }); * }); */ /** * When the group rotate finishes, the `rotateGroupEnd` event is called. * @memberof Moveable.Rotatable * @event rotateGroupEnd * @param {Moveable.Rotatable.OnRotateGroupEnd} - Parameters for the `rotateGroupEnd` event * @example * import Moveable from "moveable"; * * const moveable = new Moveable(document.body, { * target: [].slice.call(document.querySelectorAll(".target")), * rotatable: true * }); * moveable.on("rotateGroupEnd", ({ targets, isDrag }) => { * console.log("onRotateGroupEnd", targets, isDrag); * }); */