UNPKG

react-moveable

Version:

A React Component that create Moveable, Draggable, Resizable, Scalable, Rotatable, Warpable, Pinchable, Groupable.

668 lines (607 loc) 19.4 kB
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); }