UNPKG

react-moveable

Version:

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

347 lines (296 loc) 11.4 kB
import { Able, MoveableManagerInterface, MoveableGroupInterface } from "../types"; import { getWindow, hasClass, IObject } from "@daybrush/utils"; import { convertDragDist, defaultSync, getRefTarget } from "../utils"; import Gesto, { GestoOptions } from "gesto"; import BeforeRenderable from "../ables/BeforeRenderable"; import Renderable from "../ables/Renderable"; export function triggerAble( moveable: MoveableManagerInterface, moveableAbles: Able[], eventOperations: string[], eventAffix: string, eventType: any, e: any, requestInstant?: boolean, ) { // pre setting e.clientDistX = e.distX; e.clientDistY = e.distY; const isStart = eventType === "Start"; const isEnd = eventType === "End"; const isAfter = eventType === "After"; const target = moveable.state.target; const isRequest = e.isRequest; const isControl = eventAffix.indexOf("Control") > -1; if ( !target || (isStart && isControl && !isRequest && moveable.areaElement === e.inputEvent.target) ) { return false; } const ables: Able[] = [...moveableAbles]; if (isRequest) { const requestAble = e.requestAble; if (!ables.some(able => able.name === requestAble)) { ables.push(...moveable.props.ables!.filter(able => able.name === requestAble)); } } if (!ables.length || ables.every(able => able.dragRelation)) { return false; } // "drag" "Control" "After" const inputEvent = e.inputEvent; let inputTarget: Element; if (isEnd && inputEvent) { inputTarget = document.elementFromPoint(e.clientX, e.clientY) || inputEvent.target; } let isDragStop = false; const stop = () => { isDragStop = true; e.stop?.(); }; const isFirstStart = isStart && ( !moveable.targetGesto || !moveable.controlGesto || (!moveable.targetGesto.isFlag() || !moveable.controlGesto.isFlag()) ); if (isFirstStart) { moveable.updateRect(eventType, true, false); } // trigger ables const datas = e.datas; const gestoType = isControl ? "controlGesto" : "targetGesto"; const prevGesto = moveable[gestoType]; const trigger = (able: any, eventName: string, conditionName?: string) => { if (!(eventName in able) || prevGesto !== moveable[gestoType]) { return false; } const ableName = able.name; const nextDatas = datas[ableName] || (datas[ableName] = {}); if (isStart) { nextDatas.isEventStart = !conditionName || !able[conditionName] || able[conditionName](moveable, e); } if (!nextDatas.isEventStart) { return false; } const result = able[eventName](moveable, { ...e, stop, datas: nextDatas, originalDatas: datas, inputTarget, }); (moveable as any)._emitter.off(); if (isStart && result === false) { nextDatas.isEventStart = false; } return result; }; // unset ables for first drag start if (isFirstStart) { ables.forEach(able => { able.unset && able.unset(moveable); }); } // BeforeRenderable trigger(BeforeRenderable, `drag${eventAffix}${eventType}`); let forceEndedCount = 0; let updatedCount = 0; eventOperations.forEach(eventOperation => { if (isDragStop) { return false; } const eventName = `${eventOperation}${eventAffix}${eventType}`; const conditionName = `${eventOperation}${eventAffix}Condition`; if (eventType === "" && !isRequest) { // Convert distX, distY convertDragDist(moveable.state, e); } // const isGroup = eventAffix.indexOf("Group") > -1; let eventAbles: Able[] = ables.filter((able: any) => able[eventName]); eventAbles = eventAbles.filter((able, i) => { return able.name && eventAbles.indexOf(able) === i; }); const results = eventAbles.filter(able => trigger(able, eventName, conditionName)); const isUpdate = results.length; // end ables if (isDragStop) { ++forceEndedCount; } if (isUpdate) { ++updatedCount; } if (!isDragStop && isStart && eventAbles.length && !isUpdate) { forceEndedCount += eventAbles.filter(able => { const ableName = able.name; const nextDatas = datas[ableName]; if (nextDatas.isEventStart) { if (able.dragRelation === "strong") { return false; } // stop drag return true; } // pre stop drag return false; }).length ? 1 : 0; } }); if (!isAfter || updatedCount) { trigger(Renderable, `drag${eventAffix}${eventType}`); } // stop gesto condition const isForceEnd = prevGesto !== moveable[gestoType] || forceEndedCount === eventOperations.length; if (isEnd || isDragStop || isForceEnd) { moveable.state.gestos = {}; if ((moveable as MoveableGroupInterface).moveables) { (moveable as MoveableGroupInterface).moveables.forEach(childMoveable => { childMoveable.state.gestos = {}; }); } ables.forEach(able => { able.unset && able.unset(moveable); }); } if (isStart && !isForceEnd && !isRequest && updatedCount && moveable.props.preventDefault) { e?.preventDefault(); } if (moveable.isUnmounted || isForceEnd) { return false; } if ((!isStart && updatedCount && !requestInstant) || isEnd) { const flushSync = moveable.props.flushSync || defaultSync; flushSync(() => { moveable.updateRect(isEnd ? eventType : "", true, false); moveable.forceUpdate(); }); } if (!isStart && !isEnd && !isAfter && updatedCount && !requestInstant) { triggerAble(moveable, moveableAbles, eventOperations, eventAffix, eventType + "After", e); } return true; } export function checkMoveableTarget(moveable: MoveableManagerInterface, isControl?: boolean) { return (e: { inputEvent: Event }, target: EventTarget | null = e.inputEvent.target) => { const eventTarget = target as Element; const areaElement = moveable.areaElement; const dragTargetElement = (moveable as any)._dragTarget; if (!dragTargetElement || (!isControl && moveable.controlGesto?.isFlag())) { return false; } return eventTarget === dragTargetElement || dragTargetElement.contains(eventTarget) || eventTarget === areaElement || (!moveable.isMoveableElement(eventTarget) && !moveable.controlBox.contains(eventTarget)) || hasClass(eventTarget, "moveable-area") || hasClass(eventTarget, "moveable-padding") || hasClass(eventTarget, "moveable-edgeDraggable"); }; } export function getTargetAbleGesto( moveable: MoveableManagerInterface, moveableTarget: HTMLElement | SVGElement, eventAffix: string, ) { const controlBox = moveable.controlBox; const targets: Array<HTMLElement | SVGElement> = []; const props = moveable.props; const dragArea = props.dragArea; const target = moveable.state.target; const dragTarget = props.dragTarget; targets.push(controlBox); if (!dragArea || dragTarget) { targets.push(moveableTarget); } if (!dragArea && dragTarget && target && moveableTarget !== target && props.dragTargetSelf) { targets.push(target); } const checkTarget = checkMoveableTarget(moveable); return getAbleGesto(moveable, targets, "targetAbles", eventAffix, { dragStart: checkTarget, pinchStart: checkTarget, }); } export function getControlAbleGesto( moveable: MoveableManagerInterface, eventAffix: string, ) { const controlBox = moveable.controlBox; const targets: Array<HTMLElement | SVGElement> = []; targets.push(controlBox); const checkTarget = checkMoveableTarget(moveable, true); const checkControlTarget = (e: any, target: EventTarget | null = e.inputEvent.target) => { if (target === controlBox) { return true; } const result = checkTarget(e, target); return !result; }; return getAbleGesto(moveable, targets, "controlAbles", eventAffix, { dragStart: checkControlTarget, pinchStart: checkControlTarget, }); } export function getAbleGesto( moveable: MoveableManagerInterface, target: HTMLElement | SVGElement | Array<HTMLElement | SVGElement>, ableType: string, eventAffix: string, conditionFunctions: IObject<any> = {}, ) { const isTargetAbles = ableType === "targetAbles"; const { pinchOutside, pinchThreshold, preventClickEventOnDrag, preventClickDefault, checkInput, dragFocusedInput, preventDefault = true, preventRightClick = true, preventWheelClick = true, dragContainer: dragContaienrOption, } = moveable.props; const dragContainer = getRefTarget(dragContaienrOption, true); const options: GestoOptions = { preventDefault, preventRightClick, preventWheelClick, container: dragContainer || getWindow(moveable.getControlBoxElement()), pinchThreshold, pinchOutside, preventClickEventOnDrag: isTargetAbles ? preventClickEventOnDrag : false, preventClickEventOnDragStart: isTargetAbles ? preventClickDefault : false, preventClickEventByCondition: isTargetAbles ? null : (e: MouseEvent) => { return moveable.controlBox.contains(e.target as Element); }, checkInput: isTargetAbles ? checkInput : false, dragFocusedInput, }; const gesto = new Gesto(target!, options); const isControl = eventAffix === "Control"; ["drag", "pinch"].forEach(eventOperation => { ["Start", "", "End"].forEach(eventType => { gesto.on(`${eventOperation}${eventType}` as any, e => { const eventName = e.eventType; const isPinchScheduled = eventOperation === "drag" && e.isPinch; if (conditionFunctions[eventName] && !conditionFunctions[eventName](e)) { e.stop(); return; } if (isPinchScheduled) { return; } const eventOperations = eventOperation === "drag" ? [eventOperation] : ["drag", eventOperation]; const moveableAbles: Able[] = [...(moveable as any)[ableType]]; const result = triggerAble(moveable, moveableAbles, eventOperations, eventAffix, eventType, e); if (!result) { e.stop(); } else if (moveable.props.stopPropagation || (eventType === "Start" && isControl)) { e?.inputEvent?.stopPropagation(); } }); }); }); return gesto; }