From 3ac47da798cb655d37903ea9a8574ae62d1a7844 Mon Sep 17 00:00:00 2001 From: Daybrush Date: Fri, 11 Nov 2022 01:08:35 +0900 Subject: [PATCH] fix: fix snap bug for rotated group #786 --- .../src/react-moveable/MoveableGroup.tsx | 55 ++++++++---- .../src/react-moveable/gesto/CustomGesto.ts | 2 +- .../src/react-moveable/types.ts | 2 + .../src/react-moveable/utils.tsx | 85 +++++++++++++++++++ .../5-Snap&Bound/0-Snap&Bound.stories.tsx | 9 +- .../5-Snap&Bound/ReactSnapElementsApp.tsx | 21 ++--- .../ReactSnapElementsGroupApp.tsx | 33 +++++++ .../9-Clippable/Deafult.stories.tsx | 2 +- .../9Z-etc/apps/ReactRotateClippableApp.tsx | 4 +- 9 files changed, 175 insertions(+), 38 deletions(-) create mode 100644 packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsGroupApp.tsx diff --git a/packages/react-moveable/src/react-moveable/MoveableGroup.tsx b/packages/react-moveable/src/react-moveable/MoveableGroup.tsx index 0690570cd..9aaa1e0e3 100644 --- a/packages/react-moveable/src/react-moveable/MoveableGroup.tsx +++ b/packages/react-moveable/src/react-moveable/MoveableGroup.tsx @@ -4,7 +4,10 @@ import ChildrenDiffer from "@egjs/children-differ"; import { getAbleGesto, getTargetAbleGesto } from "./gesto/getAbleGesto"; import Groupable from "./ables/Groupable"; import { MIN_NUM, MAX_NUM, TINY_NUM } from "./consts"; -import { getAbsolutePosesByState, equals, unset } from "./utils"; +import { + getAbsolutePosesByState, equals, unset, rotatePosesInfo, + convertTransformOriginArray, +} from "./utils"; import { minus, plus } from "@scena/matrix"; import { getIntersectionPointsByConstants, getMinMaxs } from "overlap-area"; import { find, isArray, throttle } from "@daybrush/utils"; @@ -39,6 +42,8 @@ function getGroupRect(parentPoses: number[][][], rotation: number): GroupRect { pos4, minX: 0, minY: 0, + maxX: 0, + maxY: 0, width, height, rotation, @@ -144,7 +149,7 @@ function getGroupRect(parentPoses: number[][][], rotation: number): GroupRect { [pos1, pos2, pos3, pos4] = changedX; } - const { minX, minY } = getMinMaxs([pos1, pos2, pos3, pos4]); + const { minX, minY, maxX, maxY } = getMinMaxs([pos1, pos2, pos3, pos4]); return { pos1, @@ -155,6 +160,8 @@ function getGroupRect(parentPoses: number[][][], rotation: number): GroupRect { height, minX, minY, + maxX, + maxY, rotation, }; } @@ -290,14 +297,32 @@ class MoveableGroup extends MoveableManager { } this.renderGroupRects = renderGroupRects; + const transformOrigin = this.transformOrigin; const rotation = this.rotation; const scale = this.scale; const { width, height, minX, minY } = rootGroupRect; + const posesInfo = rotatePosesInfo( + [ + [0, 0], + [width, 0], + [0, height], + [width, height], + ], + convertTransformOriginArray(transformOrigin, width, height), + this.rotation / 180 * Math.PI, + ); - // tslint:disable-next-line: max-line-length - const transform = `rotate(${rotation}deg) scale(${scale[0] >= 0 ? 1 : -1}, ${scale[1] >= 0 ? 1 : -1})`; - target.style.cssText += `left:0px;top:0px; transform-origin: ${this.transformOrigin}; width:${width}px; height:${height}px;` - + `transform:${transform}`; + const { minX: deltaX, minY: deltaY } = getMinMaxs(posesInfo.result); + const rotateScale = ` rotate(${rotation}deg)` + + ` scale(${scale[0] >= 0 ? 1 : -1}, ${scale[1] >= 0 ? 1 : -1})`; + const transform = `translate(${-deltaX}px, ${-deltaY}px)${rotateScale}`; + + this.controlBox.getElement().style.transform + = `translate3d(${minX}px, ${minY}px, ${this.props.translateZ || 0})`; + target.style.cssText += `left:0px;top:0px;` + + `transform-origin:${transformOrigin};` + + `width:${width}px;height:${height}px;` + + `transform: ${transform}`; state.width = width; state.height = height; @@ -320,25 +345,23 @@ class MoveableGroup extends MoveableManager { const minPos = getMinMaxs([pos1, pos2, pos3, pos4]); const delta = [minPos.minX, minPos.minY]; + const direction = scale[0] * scale[1] > 0 ? 1 : -1; + info.pos1 = minus(pos1, delta); info.pos2 = minus(pos2, delta); info.pos3 = minus(pos3, delta); info.pos4 = minus(pos4, delta); + // info.left = info.left + delta[0]; + // info.top = info.top + delta[1]; info.left = minX - info.left! + delta[0]; info.top = minY - info.top! + delta[1]; info.origin = minus(plus(pos, info.origin!), delta); info.beforeOrigin = minus(plus(pos, info.beforeOrigin!), delta); info.originalBeforeOrigin = plus(pos, info.originalBeforeOrigin!); - // info.transformOrigin = minus(plus(pos, info.transformOrigin!), delta); - - const clientRect = info.targetClientRect!; - const direction = scale[0] * scale[1] > 0 ? 1 : -1; - - clientRect.top += info.top - state.top; - clientRect.left += info.left - state.left; - - target.style.transform = `translate(${-delta[0]}px, ${-delta[1]}px) ${transform}`; - + info.transformOrigin = minus(plus(pos, info.transformOrigin!), delta); + target.style.transform + = `translate(${-deltaX - delta[0]}px, ${-deltaY - delta[1]}px)` + + rotateScale; this.updateState( { ...info, diff --git a/packages/react-moveable/src/react-moveable/gesto/CustomGesto.ts b/packages/react-moveable/src/react-moveable/gesto/CustomGesto.ts index 318436949..7ac621483 100644 --- a/packages/react-moveable/src/react-moveable/gesto/CustomGesto.ts +++ b/packages/react-moveable/src/react-moveable/gesto/CustomGesto.ts @@ -9,7 +9,7 @@ export function setCustomDrag( isConvert: boolean, ableName = "draggable", ) { - const result = state.gestos[ableName].move(delta, e.inputEvent); + const result = state.gestos[ableName]?.move(delta, e.inputEvent) ?? {}; const datas = result.originalDatas || result.datas; const ableDatas = datas[ableName] || (datas[ableName] = {}); diff --git a/packages/react-moveable/src/react-moveable/types.ts b/packages/react-moveable/src/react-moveable/types.ts index cc8627659..7b77c9ada 100644 --- a/packages/react-moveable/src/react-moveable/types.ts +++ b/packages/react-moveable/src/react-moveable/types.ts @@ -2843,6 +2843,8 @@ export interface GroupRect { pos4: number[]; minX: number; minY: number; + maxX: number; + maxY: number; width: number; height: number; rotation: number; diff --git a/packages/react-moveable/src/react-moveable/utils.tsx b/packages/react-moveable/src/react-moveable/utils.tsx index 5c579545a..d679e4956 100644 --- a/packages/react-moveable/src/react-moveable/utils.tsx +++ b/packages/react-moveable/src/react-moveable/utils.tsx @@ -14,6 +14,7 @@ import { createScaleMatrix, plus, convertMatrixtoCSS, + rotate, } from "@scena/matrix"; import { MoveableManagerState, Able, MoveableClientRect, @@ -1465,3 +1466,87 @@ export function getOffsetSizeDist( distHeight, }; } + +export function convertTransformUnit( + origin: string, + xy?: boolean, +): { x?: string; y?: string; value?: string; } { + if (xy) { + if (origin === "left") { + return { x: "0%", y: "50%" }; + } else if (origin === "top") { + return { x: "50%", y: "50%" }; + } else if (origin === "center") { + return { x: "50%", y: "50%" }; + } else if (origin === "right") { + return { x: "100%", y: "50%" }; + } else if (origin === "bottom") { + return { x: "50%", y: "100%" }; + } + const [left, right] = origin.split(" "); + const leftOrigin = convertTransformUnit(left || ""); + const rightOrigin = convertTransformUnit(right || ""); + const originObject = { + ...leftOrigin, + ...rightOrigin, + }; + + const nextOriginObject = { + x: "50%", + y: "50%", + }; + if (originObject.x) { + nextOriginObject.x = originObject.x; + } + if (originObject.y) { + nextOriginObject.y = originObject.y; + } + if (originObject.value) { + if (originObject.x && !originObject.y) { + nextOriginObject.y = originObject.value; + } + if (!originObject.x && originObject.y) { + nextOriginObject.x = originObject.value; + } + } + return nextOriginObject; + } + if (origin === "left") { + return { x: "0%" }; + } + if (origin === "right") { + return { x: "100%" }; + } + if (origin === "top") { + return { y: "0%" }; + } + if (origin === "bottom") { + return { y: "100%" }; + } + if (!origin) { + return {}; + } + if (origin === "center") { + return { value: "50%" }; + } + return { value: origin }; +} +export function convertTransformOriginArray(transformOrigin: string, width: number, height: number) { + const { x, y } = convertTransformUnit(transformOrigin, true); + + return [ + convertUnitSize(x!, width) || 0, + convertUnitSize(y!, height) || 0, + ]; +} + +export function rotatePosesInfo(poses: number[][], origin: number[], rad: number) { + const prevPoses = poses.map((pos) => minus(pos, origin)); + const nextPoses = prevPoses.map((pos) => rotate(pos, rad)); + + return { + prev: prevPoses, + next: nextPoses, + result: nextPoses.map(pos => plus(pos, origin)), + }; +} diff --git a/packages/react-moveable/stories/5-Snap&Bound/0-Snap&Bound.stories.tsx b/packages/react-moveable/stories/5-Snap&Bound/0-Snap&Bound.stories.tsx index 7227bf0c8..062b24d7c 100644 --- a/packages/react-moveable/stories/5-Snap&Bound/0-Snap&Bound.stories.tsx +++ b/packages/react-moveable/stories/5-Snap&Bound/0-Snap&Bound.stories.tsx @@ -35,8 +35,6 @@ group.add("Snap Elements", { argsTypes: { ...DEFAULT_SNAPPABLE_CONTROLS, ...DEFAULT_SNAPPABLE_ELEMENTS_CONTROLS, - ...DEFAULT_DRAGGABLE_CONTROLS, - ...DEFAULT_SCALABLE_CONTROLS, }, }); @@ -125,5 +123,12 @@ group.add("Set maximum distance for guidelines", { }); +group.add("Snap Elements (group)", { + app: require("./ReactSnapElementsGroupApp").default, + path: require.resolve("./ReactSnapElementsGroupApp"), +}); + + + // export * from "./9-maxSnapElement.stories"; diff --git a/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsApp.tsx b/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsApp.tsx index f3d6f1280..a61ce9ff0 100644 --- a/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsApp.tsx +++ b/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsApp.tsx @@ -39,14 +39,9 @@ export default function App(props: Record) { ) { snapThreshold={props.snapThreshold} maxSnapElementGuidelineDistance={props.maxSnapElementGuidelineDistance} elementGuidelines={[".element1", ".element2", ".element3"]} - onBeforeRenderStart={e => { - e.setTransform(e.target.style.transform); - }} - onDrag={e => { - e.target.style.transform = e.transform; - }} - onScale={e => { - e.target.style.transform = e.drag.transform; + onRender={e => { + e.target.style.cssText += e.cssText; }} /> diff --git a/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsGroupApp.tsx b/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsGroupApp.tsx new file mode 100644 index 000000000..35e1479fa --- /dev/null +++ b/packages/react-moveable/stories/5-Snap&Bound/ReactSnapElementsGroupApp.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; +import Moveable from "@/react-moveable"; + +export default function App(props: Record) { + return ( +
+
+
Target 1
+
Target 2
+
Target 3
+ { + e.events.forEach(ev => { + ev.target.style.cssText += ev.cssText; + }); + }} + /> +
+
+ ); +} diff --git a/packages/react-moveable/stories/5A-Advanced/9-Clippable/Deafult.stories.tsx b/packages/react-moveable/stories/5A-Advanced/9-Clippable/Deafult.stories.tsx index bb64993e5..d4c0d699f 100644 --- a/packages/react-moveable/stories/5A-Advanced/9-Clippable/Deafult.stories.tsx +++ b/packages/react-moveable/stories/5A-Advanced/9-Clippable/Deafult.stories.tsx @@ -11,7 +11,7 @@ group.add("Clippable with Drag, Resize, Rotate", { }); -group.add("Drag, Resize, Rotate with Clipped Area", { +group.add("Drag, Resize, Rotate with Clipped Area (Testing)", { app: require("./ReactClippedAreaApp").default, path: require.resolve("./ReactClippedAreaApp"), }); diff --git a/packages/react-moveable/stories/9Z-etc/apps/ReactRotateClippableApp.tsx b/packages/react-moveable/stories/9Z-etc/apps/ReactRotateClippableApp.tsx index b5ca7d2c5..19d61d4bc 100644 --- a/packages/react-moveable/stories/9Z-etc/apps/ReactRotateClippableApp.tsx +++ b/packages/react-moveable/stories/9Z-etc/apps/ReactRotateClippableApp.tsx @@ -30,8 +30,8 @@ export default function App() { dragWithClip={false} defaultClipPath={"inset"} clipTargetBounds={true} - clipVerticalGuidelines={[]} - clipHorizontalGuidelines={[]} + clipVerticalGuidelines={[0, 100, 200]} + clipHorizontalGuidelines={[0, 100, 200]} snapThreshold={5} onClip={(e) => { if (e.clipType === "rect") {