react-moveable
Version:
A React Component that create Moveable, Draggable, Resizable, Scalable, Rotatable, Warpable, Pinchable, Groupable.
668 lines (607 loc) • 19.4 kB
text/typescript
import {
invert, calculate, minus, plus,
convertPositionMatrix,
createScaleMatrix, multiply, fromTranslation, convertDimension,
} from "@scena/matrix";
import {
calculatePoses, getAbsoluteMatrix, getAbsolutePosesByState,
calculatePosition, calculateInversePosition, convertTransformInfo, fillCSSObject,
} from "../utils";
import { splitUnit, isArray, splitSpace, findIndex, dot, find, isString } from "@daybrush/utils";
import {
MoveableManagerState, ResizableProps, MoveableManagerInterface,
OnTransformEvent, OnTransformStartEvent, DraggableProps, OnDrag,
} from "../types";
import { setCustomDrag } from "./CustomGesto";
import { parse, parseMat } from "css-to-mat";
import { Draggable } from "../index.esm";
import { calculateElementPosition } from "../utils/calculateElementPosition";
export function calculatePointerDist(moveable: MoveableManagerInterface, e: any) {
const { clientX, clientY, datas } = e;
const {
moveableClientRect,
rootMatrix,
is3d,
pos1,
} = moveable.state;
const { left, top } = moveableClientRect;
const n = is3d ? 4 : 3;
const [posX, posY] = minus(calculateInversePosition(rootMatrix, [clientX - left, clientY - top], n), pos1);
const [distX, distY] = getDragDist({ datas, distX: posX, distY: posY });
return [distX, distY];
}
export function setDragStart(moveable: MoveableManagerInterface<any>, { datas }: any) {
const {
allMatrix,
beforeMatrix,
is3d,
left,
top,
origin,
offsetMatrix,
targetMatrix,
transformOrigin,
} = moveable.state;
const n = is3d ? 4 : 3;
datas.is3d = is3d;
datas.matrix = allMatrix;
datas.targetMatrix = targetMatrix;
datas.beforeMatrix = beforeMatrix;
datas.offsetMatrix = offsetMatrix;
datas.transformOrigin = transformOrigin;
datas.inverseMatrix = invert(allMatrix, n);
datas.inverseBeforeMatrix = invert(beforeMatrix, n);
datas.absoluteOrigin = convertPositionMatrix(plus([left, top], origin), n);
datas.startDragBeforeDist = calculate(datas.inverseBeforeMatrix, datas.absoluteOrigin, n);
datas.startDragDist = calculate(datas.inverseMatrix, datas.absoluteOrigin, n);
}
export function getTransformDirection(e: any) {
return calculateElementPosition(e.datas.beforeTransform, [50, 50], 100, 100).direction;
}
export interface OriginalDataTransformInfos {
startTransforms: string[];
nextTransforms: string[];
nextTransformAppendedIndexes: number[];
}
export function resolveTransformEvent(moveable: MoveableManagerInterface, event: any, functionName: string) {
const {
datas,
originalDatas: {
beforeRenderable: originalDatas,
},
} = event;
const index = datas.transformIndex;
const nextTransforms = originalDatas.nextTransforms as string[];
const length = nextTransforms.length;
const nextTransformAppendedIndexes: any[] = originalDatas.nextTransformAppendedIndexes;
let nextIndex = -1;
if (index === -1) {
// translate => rotate => scale
if (functionName === "translate") {
nextIndex = 0;
} else if (functionName === "rotate") {
nextIndex = findIndex(nextTransforms, text => text.match(/scale\(/g,));
}
if (nextIndex === -1) {
nextIndex = nextTransforms.length;
}
datas.transformIndex = nextIndex;
} else if (find(nextTransformAppendedIndexes, info => info.index === index && info.functionName === functionName)) {
nextIndex = index;
} else {
nextIndex = index + nextTransformAppendedIndexes.filter(info => info.index < index).length;
}
const result = convertTransformInfo(nextTransforms, moveable.state, nextIndex);
const targetFunction = result.targetFunction;
const matFunctionName = functionName === "rotate" ? "rotateZ" : functionName;
datas.beforeFunctionTexts = result.beforeFunctionTexts;
datas.afterFunctionTexts = result.afterFunctionTexts;
datas.beforeTransform = result.beforeFunctionMatrix;
datas.beforeTransform2 = result.beforeFunctionMatrix2;
datas.targetTansform = result.targetFunctionMatrix;
datas.afterTransform = result.afterFunctionMatrix;
datas.afterTransform2 = result.afterFunctionMatrix2;
datas.targetAllTransform = result.allFunctionMatrix;
if (targetFunction.functionName === matFunctionName) {
datas.afterFunctionTexts.splice(0, 1);
datas.isAppendTransform = false;
} else if (length > nextIndex) {
datas.isAppendTransform = true;
originalDatas.nextTransformAppendedIndexes = [...nextTransformAppendedIndexes, {
functionName,
index: nextIndex,
isAppend: true,
}];
}
}
export function convertTransformFormat(datas: any, value: any, dist: any) {
return `${datas.beforeFunctionTexts.join(" ")} ${datas.isAppendTransform ? dist : value} ${datas.afterFunctionTexts.join(" ")}`;
}
export function getTransformDist({ datas, distX, distY }: any) {
const [bx, by] = getBeforeDragDist({ datas, distX, distY });
// B * [tx, ty] * A = [bx, by] * targetMatrix;
// [tx, ty] = B-1 * [bx, by] * targetMatrix * A-1 * [0, 0];
const res = getTransfromMatrix(datas, fromTranslation([bx, by], 4));
return calculate(res, convertPositionMatrix([0, 0, 0], 4), 4);
}
export function getTransfromMatrix(datas: any, targetMatrix: number[], isAfter?: boolean) {
const {
beforeTransform,
afterTransform,
beforeTransform2,
afterTransform2,
targetAllTransform,
} = datas;
// B * afterTargetMatrix * A = (targetMatrix * targetAllTransform)
// afterTargetMatrix = B-1 * targetMatrix * targetAllTransform * A-1
// nextTargetMatrix = (targetMatrix * targetAllTransform)
const nextTargetMatrix
= isAfter
? multiply(targetAllTransform, targetMatrix, 4)
: multiply(targetMatrix, targetAllTransform, 4);
// res1 = B-1 * nextTargetMatrix
const res1 = multiply(invert(isAfter ? beforeTransform2 : beforeTransform, 4), nextTargetMatrix, 4);
// res3 = res2 * A-1
const afterTargetMatrix = multiply(res1, invert(isAfter ? afterTransform2 : afterTransform, 4), 4);
return afterTargetMatrix;
}
export function getBeforeDragDist({ datas, distX, distY }: any) {
// TT = BT
const {
inverseBeforeMatrix,
is3d,
startDragBeforeDist,
absoluteOrigin,
} = datas;
const n = is3d ? 4 : 3;
// ABS_ORIGIN * [distX, distY] = BM * (ORIGIN + [tx, ty])
// BM -1 * ABS_ORIGIN * [distX, distY] - ORIGIN = [tx, ty]
return minus(
calculate(
inverseBeforeMatrix,
plus(absoluteOrigin, [distX, distY]),
n,
),
startDragBeforeDist,
);
}
export function getDragDist({ datas, distX, distY }: any, isBefore?: boolean) {
const {
inverseBeforeMatrix,
inverseMatrix,
is3d,
startDragBeforeDist,
startDragDist,
absoluteOrigin,
} = datas;
const n = is3d ? 4 : 3;
return minus(
calculate(
isBefore ? inverseBeforeMatrix : inverseMatrix,
plus(absoluteOrigin, [distX, distY]),
n,
),
isBefore ? startDragBeforeDist : startDragDist,
);
}
export function getInverseDragDist({ datas, distX, distY }: any, isBefore?: boolean) {
const {
beforeMatrix,
matrix,
is3d,
startDragBeforeDist,
startDragDist,
absoluteOrigin,
} = datas;
const n = is3d ? 4 : 3;
return minus(
calculate(
isBefore ? beforeMatrix : matrix,
plus(isBefore ? startDragBeforeDist : startDragDist, [distX, distY]),
n,
),
absoluteOrigin,
);
}
export function calculateTransformOrigin(
transformOrigin: string[],
width: number,
height: number,
prevWidth: number = width,
prevHeight: number = height,
prevOrigin: number[] = [0, 0],
) {
if (!transformOrigin) {
return prevOrigin;
}
return transformOrigin.map((pos, i) => {
const { value, unit } = splitUnit(pos);
const prevSize = (i ? prevHeight : prevWidth);
const size = (i ? height : width);
if (pos === "%" || isNaN(value)) {
// no value but %
const measureRatio = prevSize ? prevOrigin[i] / prevSize : 0;
return size * measureRatio;
} else if (unit !== "%") {
return value;
}
return size * value / 100;
});
}
export function getPosIndexesByDirection(direction: number[]) {
const indexes: number[] = [];
if (direction[1] >= 0) {
if (direction[0] >= 0) {
indexes.push(3);
}
if (direction[0] <= 0) {
indexes.push(2);
}
}
if (direction[1] <= 0) {
if (direction[0] >= 0) {
indexes.push(1);
}
if (direction[0] <= 0) {
indexes.push(0);
}
}
return indexes;
}
export function getPosesByDirection(
poses: number[][],
direction: number[],
) {
/*
[-1, -1](pos1) [0, -1](pos1,pos2) [1, -1](pos2)
[-1, 0](pos1, pos3) [1, 0](pos2, pos4)
[-1, 1](pos3) [0, 1](pos3, pos4) [1, 1](pos4)
*/
return getPosIndexesByDirection(direction).map(index => poses[index]);
}
export function getPosBySingleDirection(
poses: number[][],
direction: number,
) {
const ratio = (direction + 1) / 2;
return [
dot(poses[0][0], poses[1][0], ratio, 1 - ratio),
dot(poses[0][1], poses[1][1], ratio, 1 - ratio),
];
}
export function getPosByDirection(
poses: number[][],
direction: number[],
) {
const top = getPosBySingleDirection([poses[0], poses[1]], direction[0]);
const bottom = getPosBySingleDirection([poses[2], poses[3]], direction[0]);
return getPosBySingleDirection([top, bottom], direction[1]);
}
function getDist(
startPos: number[],
matrix: number[],
width: number,
height: number,
n: number,
fixedDirection: number[],
) {
const poses = calculatePoses(matrix, width, height, n);
const fixedPos = getPosByDirection(poses, fixedDirection);
const distX = startPos[0] - fixedPos[0];
const distY = startPos[1] - fixedPos[1];
return [distX, distY];
}
export function getNextMatrix(
offsetMatrix: number[],
targetMatrix: number[],
origin: number[],
n: number,
) {
return multiply(
offsetMatrix,
getAbsoluteMatrix(targetMatrix, n, origin),
n,
);
}
export function getNextTransformMatrix(
state: MoveableManagerState<any>,
datas: any,
transform: string | number[],
isAllTransform?: boolean,
) {
const {
transformOrigin,
offsetMatrix,
is3d,
} = state;
const n = is3d ? 4 : 3;
let targetTransform!: number[];
if (isString(transform)) {
const {
beforeTransform,
afterTransform,
} = datas;
if (isAllTransform) {
targetTransform = convertDimension(parseMat(transform), 4, n);
} else {
targetTransform = convertDimension(
multiply(multiply(beforeTransform, parseMat([transform]), 4), afterTransform, 4),
4, n,
);
}
} else {
targetTransform = transform;
}
return getNextMatrix(
offsetMatrix,
targetTransform,
transformOrigin,
n,
);
}
export function scaleMatrix(
state: any,
scale: number[],
) {
const {
transformOrigin,
offsetMatrix,
is3d,
targetMatrix,
targetAllTransform,
} = state;
const n = is3d ? 4 : 3;
return getNextMatrix(
offsetMatrix,
multiply(targetAllTransform || targetMatrix, createScaleMatrix(scale, n), n),
transformOrigin,
n,
);
}
export function fillTransformStartEvent(moveable: MoveableManagerInterface, e: any): OnTransformStartEvent {
const originalDatas = getBeforeRenderableDatas(e);
return {
setTransform: (transform: string | string[], index = -1) => {
originalDatas.startTransforms = isArray(transform) ? transform : splitSpace(transform);
setTransformIndex(moveable, e, index);
},
setTransformIndex: (index: number) => {
setTransformIndex(moveable, e, index);
},
};
}
export function setDefaultTransformIndex(moveable: MoveableManagerInterface, e: any, property: string) {
const originalDatas = getBeforeRenderableDatas(e);
const startTransforms = originalDatas.startTransforms;
setTransformIndex(moveable, e, findIndex<string>(startTransforms, func => func.indexOf(`${property}(`) === 0));
}
export function setTransformIndex(moveable: MoveableManagerInterface, e: any, index: number) {
const originalDatas = getBeforeRenderableDatas(e);
const datas = e.datas;
datas.transformIndex = index;
if (index === -1) {
return;
}
const transform = originalDatas.startTransforms[index];
if (!transform) {
return;
}
const state = moveable.state;
const info = parse([transform], {
"x%": v => v / 100 * state.offsetWidth,
"y%": v => v / 100 * state.offsetHeight,
});
datas.startValue = info[0].functionValue;
}
export function fillOriginalTransform(
e: any,
transform: string,
) {
const originalDatas = getBeforeRenderableDatas(e);
originalDatas.nextTransforms = splitSpace(transform);
// originalDatas.nextTargetMatrix = parseMat(transform);
}
export function getBeforeRenderableDatas(e: any) {
return e.originalDatas.beforeRenderable;
}
export function getNextTransforms(e: any) {
const {
originalDatas: {
beforeRenderable: originalDatas,
},
} = e;
return originalDatas.nextTransforms as string[];
}
export function getNextTransformText(e: any) {
return (getNextTransforms(e) || []).join(" ");
}
export function getNextStyle(e: any) {
return getBeforeRenderableDatas(e).nextStyle;
}
export function fillTransformEvent(
moveable: MoveableManagerInterface<DraggableProps>,
nextTransform: string,
delta: number[],
isPinch: boolean,
e: any,
): OnTransformEvent {
fillOriginalTransform(e, nextTransform);
const drag = Draggable.drag!(
moveable,
setCustomDrag(e, moveable.state, delta, isPinch, false),
) as OnDrag;
const afterTransform = drag ? drag.transform : nextTransform;
return {
transform: nextTransform,
drag: drag as OnDrag,
...fillCSSObject({
transform: afterTransform,
}, e),
afterTransform,
};
}
export function getTranslateFixedPosition(
moveable: MoveableManagerInterface<any>,
transform: string | number[],
fixedDirection: number[],
fixedOffset: number[],
datas: any,
isAllTransform?: boolean,
) {
const nextMatrix = getNextTransformMatrix(moveable.state, datas, transform, isAllTransform);
const nextFixedPosition = getDirectionOffset(
moveable,
fixedDirection,
fixedOffset,
nextMatrix,
);
return nextFixedPosition;
}
export function getTranslateDist(
moveable: MoveableManagerInterface<any>,
transform: string,
fixedDirection: number[],
fixedPosition: number[],
fixedOffset: number[],
datas: any,
isAllTransform?: boolean,
) {
const nextFixedPosition = getTranslateFixedPosition(
moveable,
transform,
fixedDirection,
fixedOffset,
datas,
isAllTransform,
);
const state = moveable.state;
const {
left,
top,
} = state;
const groupable = moveable.props.groupable;
const groupLeft = groupable ? left : 0;
const groupTop = groupable ? top : 0;
const dist = minus(fixedPosition, nextFixedPosition);
return minus(dist, [groupLeft, groupTop]);
}
export function getScaleDist(
moveable: MoveableManagerInterface<any>,
transform: string,
fixedDirection: number[],
fixedPosition: number[],
fixedOffset: number[],
datas: any,
isAllTransform?: boolean,
) {
const dist = getTranslateDist(
moveable,
transform,
fixedDirection,
fixedPosition,
fixedOffset,
datas,
isAllTransform,
);
return dist;
}
export function getOriginDirection(moveable: MoveableManagerInterface<any>) {
const {
width,
height,
transformOrigin,
} = moveable.state;
return [
-1 + transformOrigin[0] / (width / 2),
-1 + transformOrigin[1] / (height / 2),
];
}
export function getDirectionByPos(
pos: number[],
width: number,
height: number,
) {
return [
width ? -1 + pos[0] / (width / 2) : 0,
height ? -1 + pos[1] / (height / 2) : 0,
];
}
export function getDirectionOffset(
moveable: MoveableManagerInterface,
fixedDirection: number[],
fixedOffset: number[],
nextMatrix: number[] = moveable.state.allMatrix,
) {
const {
width,
height,
is3d,
} = moveable.state;
const n = is3d ? 4 : 3;
const fixedOffsetPosition = [
width / 2 * (1 + fixedDirection[0]) + fixedOffset[0],
height / 2 * (1 + fixedDirection[1]) + fixedOffset[1],
];
return calculatePosition(nextMatrix, fixedOffsetPosition, n);
}
export function getRotateDist(
moveable: MoveableManagerInterface<any>,
rotateDist: number,
datas: any,
) {
const fixedDirection = datas.fixedDirection;
const fixedPosition = datas.fixedPosition;
const fixedOffset = datas.fixedOffset;
return getTranslateDist(
moveable,
`rotate(${rotateDist}deg)`,
fixedDirection,
fixedPosition,
fixedOffset,
datas,
);
}
export function getResizeDist(
moveable: MoveableManagerInterface<any>,
width: number,
height: number,
fixedPosition: number[],
transformOrigin: string[],
datas: any,
) {
const {
groupable,
} = moveable.props;
const state = moveable.state;
const {
transformOrigin: prevOrigin,
offsetMatrix,
is3d,
width: prevWidth,
height: prevHeight,
left,
top,
} = state;
const fixedDirection = datas.fixedDirection;
const targetMatrix = datas.nextTargetMatrix || state.targetMatrix;
const n = is3d ? 4 : 3;
const nextOrigin = calculateTransformOrigin(
transformOrigin!,
width,
height,
prevWidth,
prevHeight,
prevOrigin,
);
const groupLeft = groupable ? left : 0;
const groupTop = groupable ? top : 0;
const nextMatrix = getNextMatrix(offsetMatrix, targetMatrix, nextOrigin, n);
const dist = getDist(fixedPosition, nextMatrix, width, height, n, fixedDirection);
return minus(dist, [groupLeft, groupTop]);
}
export function getAbsolutePosition(
moveable: MoveableManagerInterface<ResizableProps>,
direction: number[],
) {
return getPosByDirection(getAbsolutePosesByState(moveable.state), direction);
}