UNPKG

react-moveable

Version:

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

310 lines (278 loc) 10.1 kB
import { convertCSStoMatrix, convertDimension, createIdentityMatrix, createOriginMatrix, createScaleMatrix, } from "@scena/matrix"; import { getCachedStyle } from "../store/Store"; import { IS_WEBKIT, IS_SAFARI_ABOVE15, IS_FIREFOX, IS_CHROMIUM109 } from "../consts"; import { MatrixInfo } from "../types"; import { getOffsetInfo, getElementTransform, getTransformMatrix, getPositionFixedInfo, convert3DMatrixes, getOffsetPosInfo, getSVGMatrix, getBodyOffset, getAbsoluteMatrix, } from "../utils"; import { getDocumentBody, getDocumentElement } from "@daybrush/utils"; import { parseMat } from "css-to-mat"; export function getShadowRoot(parentElement: HTMLElement | SVGElement) { if (parentElement && parentElement.getRootNode) { const rootNode = parentElement.getRootNode(); if (rootNode.nodeType === 11) { return rootNode; } } return; } function getIndividualTransforms(getStyle: (property: string) => any) { const scale = getStyle("scale") as string; const rotate = getStyle("rotate") as string; const translate = getStyle("translate") as string; const individualTransforms: string[] = []; if (translate && translate !== "0px" && translate !== "none") { individualTransforms.push(`translate(${translate.split(/\s+/).join(",")})`); } if (rotate && rotate !== "1" && rotate !== "none") { individualTransforms.push(`rotate(${rotate})`); } if (scale && scale !== "1" && scale !== "none") { individualTransforms.push(`scale(${scale.split(/\s+/).join(",")})`); } return individualTransforms; } export interface MatrixStackInfo { zoom: number; offsetContainer: HTMLElement; matrixes: MatrixInfo[]; targetMatrix: number[]; transformOrigin: number[]; targetOrigin: number[]; is3d: boolean; hasFixed: boolean; } export function getMatrixStackInfo( target: SVGElement | HTMLElement, container?: SVGElement | HTMLElement | null, checkContainer?: boolean, ): MatrixStackInfo { let el: SVGElement | HTMLElement | null = target; const matrixes: MatrixInfo[] = []; const documentElement = getDocumentElement(target) || getDocumentBody(target); let requestEnd = !checkContainer && target === container || target === documentElement; let isEnd = requestEnd; let is3d = false; let n = 3; let transformOrigin!: number[]; let targetTransformOrigin!: number[]; let targetMatrix!: number[]; let hasFixed = false; let offsetContainer = getOffsetInfo(container, container, true).offsetParent; let zoom = 1; while (el && !isEnd) { isEnd = requestEnd; const getStyle = getCachedStyle(el); const position = getStyle("position"); const transform = getElementTransform(el); const isFixed = position === "fixed"; const individualTransforms = getIndividualTransforms(getStyle); let matrix: number[] = convertCSStoMatrix(getTransformMatrix(transform)); let offsetParent: HTMLElement; let isOffsetEnd = false; let isStatic = false; let parentClientLeft = 0; let parentClientTop = 0; let fixedClientLeft = 0; let fixedClientTop = 0; let fixedInfo: { hasTransform: boolean; fixedContainer: HTMLElement | null; } = { hasTransform: false, fixedContainer: null, }; if (isFixed) { hasFixed = true; fixedInfo = getPositionFixedInfo(el); offsetContainer = fixedInfo.fixedContainer!; } // convert 3 to 4 const length = matrix.length; if (!is3d && (length === 16 || individualTransforms.length)) { is3d = true; n = 4; convert3DMatrixes(matrixes); if (targetMatrix) { targetMatrix = convertDimension(targetMatrix, 3, 4); } } if (is3d && length === 9) { matrix = convertDimension(matrix, 3, 4); } const { tagName, hasOffset, isSVG, origin, targetOrigin, offset: offsetPos, } = getOffsetPosInfo(el, target); let [ offsetLeft, offsetTop, ] = offsetPos; // no target with svg if (tagName === "svg" && !(el as SVGSVGElement).ownerSVGElement && targetMatrix) { // scale matrix for svg's SVGElements. matrixes.push({ type: "target", target: el, matrix: getSVGMatrix(el as SVGSVGElement, n), }); matrixes.push({ type: "offset", target: el, matrix: createIdentityMatrix(n), }); } const targetZoom = parseFloat(getStyle("zoom")) || 1; if (isFixed) { offsetParent = fixedInfo.fixedContainer!; isOffsetEnd = true; } else { const offsetInfo = getOffsetInfo(el, container, false, true, getStyle); const offsetZoom = offsetInfo.offsetZoom; offsetParent = offsetInfo.offsetParent; isOffsetEnd = offsetInfo.isEnd; isStatic = offsetInfo.isStatic; zoom *= offsetZoom; if ((offsetInfo.isCustomElement || offsetZoom !== 1) && isStatic) { offsetLeft -= offsetParent.offsetLeft; offsetTop -= offsetParent.offsetTop; } else if (IS_FIREFOX || IS_CHROMIUM109) { const parentSlotElement = offsetInfo.parentSlotElement; if (parentSlotElement) { let customOffsetParent: HTMLElement | null = offsetParent; let customOffsetLeft = 0; let customOffsetTop = 0; while (customOffsetParent) { if (!getShadowRoot(customOffsetParent)) { break; } customOffsetLeft += customOffsetParent.offsetLeft; customOffsetTop += customOffsetParent.offsetTop; customOffsetParent = customOffsetParent.offsetParent as HTMLElement; } offsetLeft -= customOffsetLeft; offsetTop -= customOffsetTop; } } } if ( IS_WEBKIT && !IS_SAFARI_ABOVE15 && hasOffset && !isSVG && isStatic && (position === "relative" || position === "static") ) { offsetLeft -= offsetParent.offsetLeft; offsetTop -= offsetParent.offsetTop; requestEnd = requestEnd || isOffsetEnd; } if (isFixed) { if (hasOffset && fixedInfo.hasTransform) { // border fixedClientLeft = offsetParent.clientLeft; fixedClientTop = offsetParent.clientTop; } } else { if (hasOffset && offsetContainer !== offsetParent) { // border parentClientLeft = offsetParent.clientLeft; parentClientTop = offsetParent.clientTop; } if (hasOffset && offsetParent === documentElement) { const margin = getBodyOffset(el, false); offsetLeft += margin[0]; offsetTop += margin[1]; } } matrixes.push({ type: "target", target: el, matrix: getAbsoluteMatrix(matrix, n, origin), }); if (individualTransforms.length) { matrixes.push({ type: "offset", target: el, matrix: createIdentityMatrix(n), }); matrixes.push({ type: "target", target: el, matrix: getAbsoluteMatrix(parseMat(individualTransforms), n, origin), }); } if (hasOffset) { const isElementTarget = el === target; const scrollLeft = isElementTarget ? 0 : el.scrollLeft; const scrollTop = isElementTarget ? 0 : el.scrollTop; matrixes.push({ type: "offset", target: el, matrix: createOriginMatrix([ offsetLeft - scrollLeft + parentClientLeft - fixedClientLeft, offsetTop - scrollTop + parentClientTop - fixedClientTop, ], n), }); } else { // svg matrixes.push({ type: "offset", target: el, origin, }); } // transform으로 계산되지 않는 zoom을 위한 (0, 0) 을 기준 matrix 추가. if (targetZoom !== 1) { matrixes.push({ type: "zoom", target: el, matrix: getAbsoluteMatrix(createScaleMatrix([targetZoom, targetZoom], n), n, [0, 0]), }); } if (!targetMatrix) { targetMatrix = matrix; } if (!transformOrigin) { transformOrigin = origin; } if (!targetTransformOrigin) { targetTransformOrigin = targetOrigin; } if (isEnd || isFixed) { break; } else { el = offsetParent; requestEnd = isOffsetEnd; } if (!checkContainer || el === documentElement) { isEnd = requestEnd; } } if (!targetMatrix) { targetMatrix = createIdentityMatrix(n); } if (!transformOrigin) { transformOrigin = [0, 0]; } if (!targetTransformOrigin) { targetTransformOrigin = [0, 0]; } return { zoom, offsetContainer, matrixes, targetMatrix, transformOrigin, targetOrigin: targetTransformOrigin, is3d, hasFixed, }; }