From 371b415a5b541f3d3dc5c1b8bf9a21482e8fee54 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:06:53 +0200 Subject: [PATCH 01/23] feat: added new hook to handle global events --- .../react-mops/src/hooks/event-listener.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 packages/react-mops/src/hooks/event-listener.ts diff --git a/packages/react-mops/src/hooks/event-listener.ts b/packages/react-mops/src/hooks/event-listener.ts new file mode 100644 index 0000000..ec60e09 --- /dev/null +++ b/packages/react-mops/src/hooks/event-listener.ts @@ -0,0 +1,19 @@ +import React from "react"; + +export const useEventListener = (type, listener, context, dependencies) => { + React.useEffect(() => { + context.addEventListener(type, listener); + return () => { + context.removeEventListener(type, listener); + }; + }, [type, listener, context, ...dependencies]); +}; + +export const useEventListeners = (types, listener, context, dependencies) => { + React.useEffect(() => { + types.map(type => context.addEventListener(type, listener)); + return () => { + types.map(type => context.removeEventListener(type, listener)); + }; + }, [types, listener, context, ...dependencies]); +}; From 6fda3a53bdf3e32b30934947637b75c67d6a6033 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:07:32 +0200 Subject: [PATCH 02/23] feat: added new hook for keys --- packages/react-mops/src/hooks/keys.ts | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/react-mops/src/hooks/keys.ts diff --git a/packages/react-mops/src/hooks/keys.ts b/packages/react-mops/src/hooks/keys.ts new file mode 100644 index 0000000..53e735a --- /dev/null +++ b/packages/react-mops/src/hooks/keys.ts @@ -0,0 +1,59 @@ +import React from "react"; +import {useEventListener, useEventListeners} from "./event-listener"; + +enum KEYS { + Alt = "Alt", + Shift = "Shift", + Meta = "Meta", + Control = "Control" +} + +export const useKey = key => { + const [isActive, setActive] = React.useState(false); + if ("window" in global) { + // Activate on keydown + useEventListener( + "keydown", + (event: KeyboardEvent) => { + if (event.key === key) { + setActive(true); + } + }, + document, + [setActive, key] + ); + + // Deactivate on keyup + useEventListener( + "keyup", + (event: KeyboardEvent) => { + if (event.key === key) { + setActive(false); + } + }, + document, + [setActive, key] + ); + + // Deactivate on window blur and focus + useEventListeners( + ["focus", "blur"], + () => { + setActive(false); + }, + window, + [setActive] + ); + + return isActive; + } + return false; +}; + +export const useMeta = () => useKey(KEYS.Meta); + +export const useControl = () => useKey(KEYS.Control); + +export const useShift = () => useKey(KEYS.Shift); + +export const useAlt = () => useKey(KEYS.Alt); From 78c63c4c82b859c9942b6397f39de8ee2e7e4796 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:08:30 +0200 Subject: [PATCH 03/23] feat: added hook for pointer --- packages/react-mops/src/hooks/pointer.ts | 69 ++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 packages/react-mops/src/hooks/pointer.ts diff --git a/packages/react-mops/src/hooks/pointer.ts b/packages/react-mops/src/hooks/pointer.ts new file mode 100644 index 0000000..546d208 --- /dev/null +++ b/packages/react-mops/src/hooks/pointer.ts @@ -0,0 +1,69 @@ +import React from "react"; +import {useEventListener, useEventListeners} from "./event-listener"; + +export const usePointer = ({onMouseMove, onMouseUp, onTouchEnd, onTouchMove}) => { + const [isDown, setDown] = React.useState(false); + + if ("window" in global) { + useEventListener( + "mousemove", + (event: MouseEvent) => { + if (isDown) { + onMouseMove(event); + } + }, + document, + [isDown, onMouseMove] + ); + + useEventListener( + "touchmove", + (event: TouchEvent) => { + if (isDown) { + onTouchMove(event); + } + }, + document, + [isDown, onTouchMove] + ); + + // Deactivate on mouseup + useEventListener( + "mouseup", + (event: MouseEvent) => { + if (isDown) { + setDown(false); + onMouseUp(event); + } + }, + document, + [isDown, setDown, onMouseUp] + ); + + // Deactivate on touchend + useEventListener( + "touchend", + (event: TouchEvent) => { + if (isDown) { + setDown(false); + onTouchEnd(event); + } + }, + document, + [isDown, setDown, onTouchEnd] + ); + + // Deactivate on window blur and focus + useEventListeners( + ["focus", "blur"], + () => { + setDown(false); + }, + window, + [setDown] + ); + + return setDown as React.Dispatch>; + } + return (() => undefined) as React.Dispatch>; +}; From 925549a83e16db8c22ab226d00944bd377b37c49 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:09:16 +0200 Subject: [PATCH 04/23] feat: added utils --- packages/react-mops/src/utils.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-mops/src/utils.ts b/packages/react-mops/src/utils.ts index 3bd2215..079903f 100644 --- a/packages/react-mops/src/utils.ts +++ b/packages/react-mops/src/utils.ts @@ -38,18 +38,22 @@ export const coordinatesToDeg = ( }; export const normalize = n => { - const rounded = Math.round(n * 10000) / 10000; + const rounded = Math.round(n * 1000000000) / 1000000000; if (rounded === 0 || rounded === -0) { return 0; } - return rounded; + return n; }; -export const polarToCartesian = (deg: number, radius: number = 1): Mops.PositionModel => { - const y = sin(deg) * radius; - const x = cos(deg) * radius; - return {x, y}; -}; +export const polarToCartesian = (deg: number, radius: number = 1): Mops.PositionModel => ({ + x: cos(deg) * radius, + y: sin(deg) * radius +}); + +export const cartesianToPolar = ({x, y}: Mops.PositionModel) => ({ + deg: to360(atan2(y, x)), + radius: getHypotenuse(y, x) +}); /** * Convert degrees to radians @@ -165,3 +169,5 @@ export const inRange = (value: number, min: number, max: number) => value >= min const fallback = (...n: number[]) => n[0]; export const chooseFn = (a: number, b: number = 0): ((...values: number[]) => number) => a > b ? Math.min : b > a ? Math.max : fallback; + +export const steps = (value: number, step: number) => Math.round(value / step) * step; From 7713f7840e71324a44118a1a360fc6558e8fb089 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:09:49 +0200 Subject: [PATCH 05/23] feat: added rotation hook --- packages/react-mops/src/hooks/rotate.ts | 61 +++++++++++++ packages/react-mops/src/hooks/rotation.ts | 106 ++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 packages/react-mops/src/hooks/rotate.ts create mode 100644 packages/react-mops/src/hooks/rotation.ts diff --git a/packages/react-mops/src/hooks/rotate.ts b/packages/react-mops/src/hooks/rotate.ts new file mode 100644 index 0000000..bd298b5 --- /dev/null +++ b/packages/react-mops/src/hooks/rotate.ts @@ -0,0 +1,61 @@ +import React from "react"; +import {Mops} from "../types"; +import {steps as toStep, to360} from "../utils"; +import {useRotation} from "./rotation"; + +interface Props { + steps?: boolean; + step: number; + deg?: number; + onRotateEnd?: (o: Partial) => void; + onRotateStart?: (o: Partial) => void; + onRotate?: (o: Partial) => void; +} + +export const useRotate = ( + initialState: number = 0, + { steps, step = 15, onRotateEnd, onRotate, onRotateStart}: Props +) => { + const ref = React.useRef(); + const [initialAngle, setInitialAngle] = React.useState(initialState); + const [angle, setAngle] = React.useState(initialAngle); + const handleRotate = React.useCallback( + d => { + const newAngle = to360(toStep(initialAngle + d, step && steps ? step : 1)); + setAngle(newAngle); + if (onRotate) { + onRotate({rotation: {x: 0, y: 0, z: newAngle}}); + } + }, + [step, steps, setAngle, initialAngle] + ); + const handleRotateEnd = React.useCallback( + d => { + const newAngle = to360(toStep(initialAngle + d, step && steps ? step : 1)); + setAngle(newAngle); + setInitialAngle(newAngle); + if (onRotateEnd) { + onRotateEnd({rotation: {x: 0, y: 0, z: newAngle}}); + } + }, + [step, steps, setAngle, initialAngle, setInitialAngle, onRotateEnd] + ); + const handleRotateStart = React.useCallback(() => { + if (onRotateStart) { + onRotateStart({rotation: {x: 0, y: 0, z: initialAngle}}); + } + }, [onRotateStart]); + const [, methods] = useRotation(ref.current, { + onRotate: handleRotate, + onRotateEnd: handleRotateEnd, + onRotateStart: handleRotateStart + }); + return [angle, {...methods, ref}] as [ + number, + { + ref: React.Ref; + onMouseDown: (event: React.MouseEvent) => void; + onTouchStart: (event: React.TouchEvent) => void; + } + ]; +}; diff --git a/packages/react-mops/src/hooks/rotation.ts b/packages/react-mops/src/hooks/rotation.ts new file mode 100644 index 0000000..91751c2 --- /dev/null +++ b/packages/react-mops/src/hooks/rotation.ts @@ -0,0 +1,106 @@ +import React from "react"; +import {cartesianToPolar, to360} from "../utils"; +import {usePointer} from "./pointer"; + +const useCartesianToPolar = (callback, node, deps) => + React.useCallback( + ({x, y}, event) => { + if (!node) { + return; + } + event.preventDefault(); + const {top, left, height, width} = node.getBoundingClientRect(); + const {deg} = cartesianToPolar({ + x: x - left - width / 2, + y: y - top - height / 2 + }); + callback(deg); + }, + [node, callback, ...deps] + ); + +export const useRotation = ( + node: HTMLElement | undefined, + {onRotateStart, onRotate, onRotateEnd}, + initialState: number = 0 +) => { + const [initialRotation, setInitialRotation] = React.useState(initialState); + const [rotation, setRotation] = React.useState(initialRotation); + const handleMove = useCartesianToPolar( + newRotation => { + const deg = to360(newRotation - initialRotation); + setRotation(deg); + if (onRotate) { + onRotate(deg); + } + }, + node, + [onRotate] + ); + const handleUp = useCartesianToPolar( + newRotation => { + const deg = to360(newRotation - initialRotation); + setRotation(deg); + if (onRotateEnd) { + onRotateEnd(deg); + } + }, + node, + [onRotateEnd] + ); + const onMouseMove = React.useCallback( + (event: MouseEvent) => handleMove({x: event.clientX, y: event.clientY}, event), + [handleMove] + ); + const onTouchMove = React.useCallback( + (event: TouchEvent) => + handleMove({x: event.touches[0].clientX, y: event.touches[0].clientY}, event), + [handleMove] + ); + const handleDown = useCartesianToPolar( + deg => { + setInitialRotation(deg); + if (onRotateStart) { + onRotateStart(deg); + } + }, + node, + [onRotateStart] + ); + const onMouseDown = React.useCallback( + (event: React.MouseEvent) => { + setDown(true); + handleDown({x: event.clientX, y: event.clientY}, event); + }, + [handleDown] + ); + const onTouchStart = React.useCallback( + (event: React.TouchEvent) => { + setDown(true); + handleDown({x: event.touches[0].clientX, y: event.touches[0].clientY}, event); + }, + [handleDown] + ); + const onMouseUp = React.useCallback( + (event: MouseEvent) => handleUp({x: event.clientX, y: event.clientY}, event), + [handleUp] + ); + const onTouchEnd = React.useCallback( + (event: TouchEvent) => + handleUp({x: event.touches[0].clientX, y: event.touches[0].clientY}, event), + [handleUp] + ); + const setDown = usePointer({ + onMouseMove, + onMouseUp, + onTouchEnd, + onTouchMove + }) as React.Dispatch>; + return [rotation, {onMouseDown, onTouchStart}] as [ + number, + { + onMouseDown?: (event: React.MouseEvent) => void; + onTouchStart?: (event: React.TouchEvent) => void; + } + ]; +}; From 4a7529d611c361d78cd8d2458986e991346bc2ea Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:10:15 +0200 Subject: [PATCH 06/23] feat: added offset hook --- packages/react-mops/src/hooks/offset.ts | 88 +++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 packages/react-mops/src/hooks/offset.ts diff --git a/packages/react-mops/src/hooks/offset.ts b/packages/react-mops/src/hooks/offset.ts new file mode 100644 index 0000000..228ec0a --- /dev/null +++ b/packages/react-mops/src/hooks/offset.ts @@ -0,0 +1,88 @@ +import React from "react"; +import {usePointer} from "./pointer"; + +export const useOffset = ({onDragStart, onDrag, onDragEnd}, initialState = {x: 0, y: 0}) => { + const [initialOffset, setInitialOffset] = React.useState<{x: number; y: number}>(initialState); + const [offset, setOffset] = React.useState<{x: number; y: number}>(initialOffset); + const handleMove = React.useCallback( + pointer => { + const coords = { + x: pointer.x - initialOffset.x, + y: pointer.y - initialOffset.y + }; + setOffset(coords); + onDrag(coords); + }, + [setOffset, initialOffset, onDrag] + ); + const onMouseMove = React.useCallback( + (event: MouseEvent) => { + event.preventDefault(); + handleMove({x: event.clientX, y: event.clientY}); + }, + [handleMove] + ); + const onTouchMove = React.useCallback( + (event: TouchEvent) => { + event.preventDefault(); + handleMove({x: event.touches[0].clientX, y: event.touches[0].clientY}); + }, + [handleMove] + ); + const handleUp = React.useCallback( + pointer => { + const coords = { + x: pointer.x - initialOffset.x, + y: pointer.y - initialOffset.y + }; + setOffset(coords); + onDragEnd(coords); + }, + [setOffset, initialOffset, onDragEnd] + ); + const onMouseUp = React.useCallback( + (event: MouseEvent) => { + handleUp({x: event.clientX, y: event.clientY}); + }, + [handleUp] + ); + const onTouchEnd = React.useCallback( + (event: TouchEvent) => { + handleUp({x: event.touches[0].clientX, y: event.touches[0].clientY}); + }, + [handleUp] + ); + const handleDown = React.useCallback( + pointer => { + setDown(true); + setInitialOffset(pointer); + onDragStart({x: 0, y: 0}); + }, + [setInitialOffset, onDragStart] + ); + const onMouseDown = React.useCallback( + (event: React.MouseEvent) => { + handleDown({x: event.clientX, y: event.clientY}); + }, + [handleDown] + ); + const onTouchStart = React.useCallback( + (event: React.TouchEvent) => { + handleDown({x: event.touches[0].clientX, y: event.touches[0].clientY}); + }, + [handleDown] + ); + const setDown = usePointer({ + onMouseMove, + onMouseUp, + onTouchEnd, + onTouchMove + }) as React.Dispatch>; + return [offset, {onMouseDown, onTouchStart}] as [ + {x: number; y: number}, + { + onMouseDown?: (event: React.MouseEvent) => void; + onTouchStart?: (event: React.TouchEvent) => void; + } + ]; +}; From 0a1320fdf4b79b3d3eb89656fe8e38ddadbc1a96 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:10:41 +0200 Subject: [PATCH 07/23] feat: added position hook --- packages/react-mops/src/hooks/position.ts | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 packages/react-mops/src/hooks/position.ts diff --git a/packages/react-mops/src/hooks/position.ts b/packages/react-mops/src/hooks/position.ts new file mode 100644 index 0000000..24a6eea --- /dev/null +++ b/packages/react-mops/src/hooks/position.ts @@ -0,0 +1,70 @@ +import React from "react"; +import {useOffset} from "./offset"; + +interface Position { + x: number; + y: number; +} + +interface PositionProps { + initialPosition: Position; + setInitialPosition: React.Dispatch>; + setPosition: React.Dispatch>; + onMouseDown?: (event: React.MouseEvent) => void; + onTouchStart?: (event: React.TouchEvent) => void; +} + +interface Props { + onDragStart?: (o: any) => void; + onDragEnd?: (o: any) => void; + onDrag?: (o: any) => void; +} + +export const usePosition = ( + initialState: Position = {x: 0, y: 0}, + { onDragEnd, onDragStart, onDrag}: Props +) => { + const [initialPosition, setInitialPosition] = React.useState(initialState); + const [position, setPosition] = React.useState(initialPosition); + const handleDrag = React.useCallback( + o => { + const newPosition = { + x: initialPosition.x + o.x, + y: initialPosition.y + o.y + }; + setPosition(newPosition); + if (onDrag) { + onDrag({position: newPosition}); + } + }, + [setPosition, initialPosition, onDrag] + ); + const handleDragEnd = React.useCallback( + o => { + const newPosition = { + x: initialPosition.x + o.x, + y: initialPosition.y + o.y + }; + setPosition(newPosition); + setInitialPosition(newPosition); + if (onDragEnd) { + onDragEnd({position: newPosition}); + } + }, + [setPosition, setInitialPosition, initialPosition, onDragEnd] + ); + const handleDragStart = React.useCallback(() => { + if (onDragStart) { + onDragStart({position: initialPosition}); + } + }, [onDragStart]); + const [, methods] = useOffset({ + onDrag: handleDrag, + onDragEnd: handleDragEnd, + onDragStart: handleDragStart + }); + return [position, {...methods, initialPosition, setInitialPosition, setPosition}] as [ + Position, + PositionProps + ]; +}; From ae29906bb9ec4bdd266438ededee2ef550336ae2 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:11:14 +0200 Subject: [PATCH 08/23] feat: added resize hook --- packages/react-mops/src/hooks/size.ts | 133 ++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/react-mops/src/hooks/size.ts diff --git a/packages/react-mops/src/hooks/size.ts b/packages/react-mops/src/hooks/size.ts new file mode 100644 index 0000000..3c55ab2 --- /dev/null +++ b/packages/react-mops/src/hooks/size.ts @@ -0,0 +1,133 @@ +import React from "react"; +import {Mops} from "../types"; +import {withRotation} from "../utils"; +import {useOffset} from "./offset"; + +interface Position { + y: number; + x: number; +} +export interface Dir { + y: -1 | 1 | 0; + x: -1 | 1 | 0; +} +interface Size { + height: number; + width: number; +} +interface Props { + dir?: Dir; + minHeight?: number; + minWidth?: number; + centered?: boolean; + deg?: number; + initialPosition?: Position; + onResizeEnd?: (o: Partial) => void; + onResizeStart?: (o: Partial) => void; + onResize?: (o: Partial) => void; + setPosition?: React.Dispatch>; + setInitialPosition?: React.Dispatch>; +} +export const useSize = ( + initialState: Size, + { + centered, + deg = 0, + dir = {x: 1, y: 1}, + minHeight, + minWidth, + onResize, + onResizeEnd, + onResizeStart, + initialPosition, + setInitialPosition, + setPosition + }: Props +) => { + const [initialSize, setInitialSize] = React.useState(initialState); + const [size, setSize] = React.useState(initialSize); + const setValues = React.useCallback( + (o, end = false) => { + const r = withRotation(o.x, o.y, deg); + const sizeOffset = { + height: initialSize.height + r.y * dir.y * (centered ? 2 : 1), + width: initialSize.width + r.x * dir.x * (centered ? 2 : 1) + }; + const newSize = { + height: Math.abs(sizeOffset.height), + width: Math.abs(sizeOffset.width) + }; + const d = withRotation( + centered || dir.x === 0 ? 0 : r.x, + centered || dir.y === 0 ? 0 : r.y, + -deg + ); + const newPosition = { + x: initialPosition.x + d.x / 2, + y: initialPosition.y + d.y / 2 + }; + setPosition(newPosition); + setSize(newSize); + if (end) { + setInitialSize(newSize); + setInitialPosition(newPosition); + if (onResizeEnd) { + onResizeEnd({ + size: newSize + }); + } + } else { + if (onResize) { + onResize({ + size: newSize + }); + } + } + }, + [ + centered, + initialSize, + setSize, + setInitialSize, + dir, + deg, + setPosition, + onResizeEnd, + onResize, + initialPosition, + setInitialPosition + ] + ); + + const handleDrag = React.useCallback( + o => { + setValues(o); + }, + [setValues] + ); + const handleDragEnd = React.useCallback( + o => { + setValues(o, true); + }, + [setValues] + ); + const handleDragStart = React.useCallback(() => { + if (onResizeStart) { + onResizeStart({ + size: initialSize + }); + } + }, [onResizeStart]); + const [, methods] = useOffset({ + onDrag: handleDrag, + onDragEnd: handleDragEnd, + onDragStart: handleDragStart + }); + return [size, methods] as [ + Size, + { + onMouseDown?: (event: React.MouseEvent) => void; + onTouchStart?: (event: React.TouchEvent) => void; + } + ]; +}; From 38534217b68ef9e1f869e3fe48f918c36244b483 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:11:47 +0200 Subject: [PATCH 09/23] feat: added snap hook --- packages/react-mops/src/hooks/snap.ts | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/react-mops/src/hooks/snap.ts diff --git a/packages/react-mops/src/hooks/snap.ts b/packages/react-mops/src/hooks/snap.ts new file mode 100644 index 0000000..cdd713b --- /dev/null +++ b/packages/react-mops/src/hooks/snap.ts @@ -0,0 +1,29 @@ +import React from "react"; +import {Mops} from "../types"; +import {getBoundingBox} from "../utils"; + +export const useSnap = ( + shouldSnap: Mops.SnapHandler[], + {position, rotation, size}: Mops.BoundingBox, + guidesContext: Mops.GuidesContext +) => + React.useMemo( + () => + shouldSnap + ? shouldSnap.reduce( + (model, fn) => ({ + ...fn( + { + position, + rotation, + size: getBoundingBox({...size, angle: rotation.z}) + }, + guidesContext, + model + ) + }), + position + ) + : position, + [position, rotation, shouldSnap, size, guidesContext] + ); From 0fe26a6f63ab2fd5066eeabb622a2a8a45a58468 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:12:08 +0200 Subject: [PATCH 10/23] added os util --- packages/react-mops/src/os.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/react-mops/src/os.ts diff --git a/packages/react-mops/src/os.ts b/packages/react-mops/src/os.ts new file mode 100644 index 0000000..85e550f --- /dev/null +++ b/packages/react-mops/src/os.ts @@ -0,0 +1,33 @@ +enum OS { + OSX = "OSX", + WINDOWS = "WINDOWS", + LINUX = "LINUX", + UNIX = "UNIX", + NODE = "NODE", + Win = "Win", + Mac = "Mac", + Unix = "X11", + Linux = "Linux" +} +const hasGlobalProp = prop => prop in global; + +const getOS = () => { + if (hasGlobalProp("navigator")) { + if (navigator.appVersion.indexOf(OS.Win) !== -1) { + return OS.WINDOWS; + } + if (navigator.appVersion.indexOf(OS.Mac) !== -1) { + return OS.OSX; + } + if (navigator.appVersion.indexOf(OS.Unix) !== -1) { + return OS.UNIX; + } + if (navigator.appVersion.indexOf(OS.Linux) !== -1) { + return OS.LINUX; + } + } + return OS.NODE; +}; + +export const isOSX = () => getOS() === OS.OSX; +export const isClient = () => hasGlobalProp("window"); From 0cb4d7cb55f26aa36ff2b8b7b4dbda15c2c4f0c1 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:18:39 +0200 Subject: [PATCH 11/23] feat: new version of Box BREAKING CHANGE: removed numerous exports, The basic API remains --- packages/react-mops/README.md | 18 +- packages/react-mops/rollup.config.js | 2 - packages/react-mops/src/box.tsx | 637 +++++++++--------- packages/react-mops/src/elements.css | 1 + packages/react-mops/src/guides/guides.tsx | 49 +- packages/react-mops/src/hooks/handle-hooks.ts | 101 --- packages/react-mops/src/hooks/index.ts | 12 +- packages/react-mops/src/hooks/listen.ts | 43 -- .../react-mops/src/hooks/mouse-event-hooks.ts | 240 ------- packages/react-mops/src/hooks/mouse-hooks.ts | 76 --- packages/react-mops/src/hooks/use-hooks.ts | 28 +- .../react-mops/src/hooks/with-down-hooks.ts | 59 -- .../react-mops/src/hooks/with-handle-hooks.ts | 251 ------- packages/react-mops/src/index.ts | 6 +- packages/react-mops/src/types.ts | 2 +- 15 files changed, 351 insertions(+), 1174 deletions(-) delete mode 100644 packages/react-mops/src/hooks/handle-hooks.ts delete mode 100644 packages/react-mops/src/hooks/listen.ts delete mode 100644 packages/react-mops/src/hooks/mouse-event-hooks.ts delete mode 100644 packages/react-mops/src/hooks/mouse-hooks.ts delete mode 100644 packages/react-mops/src/hooks/with-down-hooks.ts delete mode 100644 packages/react-mops/src/hooks/with-handle-hooks.ts diff --git a/packages/react-mops/README.md b/packages/react-mops/README.md index e149d49..f1c8d0c 100644 --- a/packages/react-mops/README.md +++ b/packages/react-mops/README.md @@ -1,11 +1,6 @@ # M.O.P.S. **M**odify **O**rientation **P**osition **S**ize -> German >> English: **Mops** >> **Pug** -> it moves, it stretches, it rolls around - -

M.O.P.S. logo

- - [Value Proposition](#value-proposition) @@ -32,18 +27,18 @@ to an Element as seen in design software like Photoshop, Sketch any many others. * [x] Resize * [x] Alt key: resize left/right, top/bottom or all directions for corners * [x] Shift key: retain aspect-ratio - * [ ] Snapping - * [ ] Touch support + * [x] Snapping + * [x] Touch support * [ ] Keyboard support * [x] Rotate * [x] Meta key: activate rotation * [x] Shift key: rotate in steps of 15 deg - * [ ] Snapping - * [ ] Touch support + * [x] Snapping + * [x] Touch support * [ ] Keyboard support * [x] Drag * [x] Snapping - * [ ] Touch support + * [x] Touch support * [ ] Keyboard support ## Installation @@ -128,6 +123,3 @@ https://codesandbox.io/s/react-mops-4cwhx ![combined](https://dekk-app.github.io/react-mops/mops_combined_01.gif) -Pug icons [created by freepik - www.freepik.com](https://www.freepik.com/free-photos-vectors/design); - - diff --git a/packages/react-mops/rollup.config.js b/packages/react-mops/rollup.config.js index 1b57deb..f481fc2 100644 --- a/packages/react-mops/rollup.config.js +++ b/packages/react-mops/rollup.config.js @@ -3,8 +3,6 @@ const url = require("rollup-plugin-url"); const {createBanner, getPlugins} = require("@ngineer/config-rollup/typescript"); const postcss = require("rollup-plugin-postcss"); -const autoprefixer = require("autoprefixer"); - module.exports = () => { const cwd = process.cwd(); const pkg = require(path.resolve(cwd, "package.json")); diff --git a/packages/react-mops/src/box.tsx b/packages/react-mops/src/box.tsx index 7f5424f..64d55e0 100644 --- a/packages/react-mops/src/box.tsx +++ b/packages/react-mops/src/box.tsx @@ -1,343 +1,316 @@ import React from "react"; -import {BoundingBox, Content, Handle, Handles, PropProvider, Wrapper} from "./elements"; -import { - listenRR, - useCursorSlice, - useDown, - useDrag, - useHandleMouse, - useHandleMouseEvent, - useHandlers, - useHandles, - useHandlesDown, - useInitialSize, - useLoaded, - useMeta, - useWithCornerHandle, - useWithDown, - useWithHandle -} from "./hooks"; +import {GuidesConsumer} from "./guides"; +import {Dir, useAlt, useMeta, usePosition, useRotate, useShift, useSize, useSnap} from "./hooks"; import {Mops} from "./types"; -import {getBoundingBox} from "./utils"; -export const Box: React.RefForwardingComponent< - HTMLElement, - Mops.BoxProps & Mops.GuidesContext -> = React.forwardRef( - ( - { - as, - children, - className, - drawBoundingBox, - drawBox, - isResizable, - isRotatable, - isDraggable, - fullHandles, - marker, - minHeight, - minWidth, - onDrag, - onDragStart, - onDragEnd, - onResize, - onResizeStart, - onResizeEnd, - onRotate, - onRotateStart, - onRotateEnd, - position, - rotation, - scale, - showGuides, - hideGuides, - updateGuide, - addGuides, - removeGuides, - guides, - guideRequests, - shouldSnap, - size, - style, - ...props - }, - ref - ) => { - const contentRef = React.useRef(); - const [loaded, setLoaded] = React.useState(false); - const [initialSize, setInitialSize] = React.useState( - size as Mops.SizeModel - ); - const [currentSize, setSize] = React.useState(initialSize); - const [initialPosition, setInitialPosition] = React.useState(position); - const [currentPosition, setPosition] = React.useState(initialPosition); - const [initialRotation, setInitialRotation] = React.useState(rotation); - const [currentRotation, setRotation] = React.useState(initialRotation); - const [additionalAngle, setAdditionalAngle] = React.useState(rotation); - const metaKey = useMeta(); - const { - handleDrag, - handleDragEnd, - handleDragStart, - handleResize, - handleResizeEnd, - handleResizeStart, - handleRotate, - handleRotateEnd, - handleRotateStart - } = useHandlers({ - currentPosition, - currentRotation, - currentSize, - onDrag, - onDragEnd, - onDragStart, - onResize, - onResizeEnd, - onResizeStart, - onRotate, - onRotateEnd, - onRotateStart - }); - const withHandle = useWithHandle({ - contentRef, - currentPosition, - currentRotation, - initialPosition, - initialSize, - isResizable, - minHeight, - minWidth, - scale, - setInitialPosition, - setInitialSize, - setPosition, - setSize - }); - - // const getLimit = React.useCallback( - // (radius, angle) => { - // const {x, y} = polarToCartesian(angle + initialRotation.z); - // return { - // x: (n: number) => chooseFn(x)(initialPosition.x + x * radius, n), - // y: (n: number) => chooseFn(y)( initialPosition.y + y * radius, n) - // }; - // }, - // [initialPosition, initialRotation] - // ); - // const diff = React.useMemo( - // () => ({ - // x: (initialSize.width - minWidth) / 2, - // y: (initialSize.height - minHeight) / 2 - // }), - // [initialSize, minHeight, minWidth] - // ); - // const limitLeft = React.useMemo(() => getLimit(diff.x, 0), [diff, getLimit]); - // const limitTop = React.useMemo(() => getLimit(diff.y, 90), [diff, getLimit]); - // const limitRight = React.useMemo(() => getLimit(diff.x, 180), [diff, getLimit]); - // const limitBottom = React.useMemo(() => getLimit(diff.y, 270), [diff, getLimit]); - // const limitTopLeft = React.useMemo(() => { - // const distance = getHypotenuse(diff.y, diff.x); - // const angle = atan2(diff.y, diff.x); - // return getLimit(distance, angle); - // }, [diff, getLimit]); - - const withCornerHandle = useWithCornerHandle({ - currentRotation, - initialPosition, - initialSize, - withHandle - }); - - const { - isBottomDown, - isBottomLeftDown, - isBottomRightDown, - isLeftDown, - isRightDown, - isTopDown, - isTopLeftDown, - isTopRightDown, - setBottomDown, - setBottomLeftDown, - setBottomRightDown, - setLeftDown, - setRightDown, - setTopDown, - setTopLeftDown, - setTopRightDown - } = useHandlesDown({ - currentRotation, - initialPosition, - initialSize, - // limitBottom, - // limitLeft, - // limitRight, - // limitTop, - // limitTopLeft, - withCornerHandle, - withHandle - }); - const handleMouse = useHandleMouse({ - addGuides, - currentRotation, - currentSize, - guideRequests, - guides, - hideGuides, - initialPosition, - removeGuides, - shouldSnap, - showGuides, - updateGuide - }); - const handleMouseEvent = useHandleMouseEvent({ - additionalAngle, - contentRef, - initialRotation, - isRotatable - }); - const {handleRotationDown, isDown, isRotationDown, setDown} = useWithDown({ - handleMouse, - handleMouseEvent, - hideGuides, - scale, - setAdditionalAngle, - setInitialPosition, - setInitialRotation, - setPosition, - setRotation - }); - const getCursorSlice = useCursorSlice(currentRotation); - const handles = useHandles({ - setBottomDown, - setBottomLeftDown, - setBottomRightDown, - setLeftDown, - setRightDown, - setTopDown, - setTopLeftDown, - setTopRightDown - }); - const wrapperStyle = { - ...currentSize, - transform: `translate3d(${currentPosition.x}px, ${currentPosition.y}px, 0) translate3d(-50%, -50%, 0)` - }; - const boxStyle = { - ...getBoundingBox({ - ...currentSize, - angle: currentRotation.z - }) - }; - const contentStyle = { - ...currentSize, - transform: `rotate3d(0, 0, 1, ${currentRotation.z}deg)` - }; - useInitialSize({contentRef, setInitialSize, setSize}); - listenRR({ - currentPosition, - currentRotation, - currentSize, - handleDrag, - handleResize, - handleRotate, - isBottomDown, - isBottomLeftDown, - isBottomRightDown, - isDown, - isLeftDown, - isRightDown, - isRotationDown, - isTopDown, - isTopLeftDown, - isTopRightDown, - loaded - }); - useDown({ - handleDragEnd, - handleDragStart, - handleResizeEnd, - handleResizeStart, - handleRotateEnd, - handleRotateStart, - isBottomDown, - isBottomLeftDown, - isBottomRightDown, - isDown, - isLeftDown, - isRightDown, - isRotationDown, - isTopDown, - isTopLeftDown, - isTopRightDown, - loaded, - metaKey - }); - useDrag({loaded, isDown, handleDragEnd, handleDragStart}); - useLoaded(setLoaded); - return ( - } - as={as} - style={{...(style || {}), ...wrapperStyle}} - isDown={isDown} - className={className}> - - } - style={contentStyle} - onMouseDown={!metaKey && isDraggable ? setDown : undefined}> - {children} - - {(isResizable || isRotatable) && ( - - - {handles.map(handle => { - return ( - - ); - })} - - - )} - - ); +const handleVariations = { + e: { + left: "100%", + top: "50%" + }, + n: { + left: "50%", + top: 0 + }, + ne: { + left: "100%", + top: 0 + }, + nw: { + left: 0, + top: 0 + }, + s: { + left: "50%", + top: "100%" + }, + se: { + left: "100%", + top: "100%" + }, + sw: { + left: 0, + top: "100%" + }, + w: { + left: 0, + top: "50%" } -); +}; -Box.defaultProps = { - as: "div", - drawBoundingBox: false, - drawBox: true, - minHeight: 40, - minWidth: 40, - position: { - x: 0, +const handleDirs = { + e: { + x: 1, y: 0 }, - rotation: { + n: { + x: 0, + y: -1 + }, + ne: { + x: 1, + y: -1 + }, + nw: { + x: -1, + y: -1 + }, + s: { x: 0, - y: 0, - z: 0 + y: 1 }, - scale: 1, - shouldSnap: [], - size: { - height: "auto", - width: "auto" + se: { + x: 1, + y: 1 + }, + sw: { + x: -1, + y: 1 + }, + w: { + x: -1, + y: 0 } }; +const Handle: React.FunctionComponent<{ + marker?: React.ClassicElement; + onMouseDown?: React.MouseEventHandler; + onTouchStart?: React.TouchEventHandler; + position: "outside" | "inside"; + variation: string; +}> = ({marker, onMouseDown, onTouchStart, position, variation}) => ( +
+ {marker} +
+); + +const Marker = () => ( +
+); + +interface BoxProps { + shouldSnap?: Mops.SnapHandler[]; + marker?: React.ComponentType; + style?: React.CSSProperties; + className?: string; + ref?: React.Ref; + onResize?: Mops.EventHandler; + onResizeStart?: Mops.EventHandler; + onResizeEnd?: Mops.EventHandler; + onRotate?: Mops.EventHandler; + onRotateStart?: Mops.EventHandler; + onRotateEnd?: Mops.EventHandler; + onDrag?: Mops.EventHandler; + onDragStart?: Mops.EventHandler; + onDragEnd?: Mops.EventHandler; + position?: Mops.PositionModel; + rotation?: Mops.RotationModel; + size?: Mops.SizeModel; + fullHandles?: boolean; + drawBox?: boolean; + minHeight?: number; + minWidth?: number; + drawBoundingBox?: boolean; + isDraggable?: boolean; + isResizable?: boolean; + isRotatable?: boolean; + scale?: number; + as?: keyof JSX.IntrinsicElements | React.ComponentType; +} +const Box: React.FunctionComponent = ({ + as: As, + children, + size: initialSize, + position: initialPosition, + rotation: initialRotation, + onRotateEnd, + onDragEnd, + onResizeEnd, + onRotateStart, + onDragStart, + onResizeStart, + onRotate, + onDrag, + onResize, + isResizable, + isDraggable, + isRotatable, + shouldSnap = [], + addGuides, + guideRequests, + guides, + hideGuides, + removeGuides, + showGuides, + updateGuide +}) => { + const [isActive, setActive] = React.useState(false); + const [dir, setDir] = React.useState({x: 0, y: 0}); + const metaKey = useMeta(); + const altKey = useAlt(); + const shiftKey = useShift(); + const [rotate, rotateProps] = useRotate(initialRotation.z, { + onRotate, + onRotateEnd: o => { + setActive(false); + const newPosition = snap as Mops.PositionModel; + positionProps.setPosition(newPosition); + positionProps.setInitialPosition(newPosition); + hideGuides(); + if (onRotateEnd) { + onRotateEnd({...o, position: newPosition}); + } + }, + onRotateStart: o => { + setActive(true); + if (onRotateStart) { + onRotateStart(o); + } + }, + step: 15, + steps: shiftKey + }); + const [position, positionProps] = usePosition(initialPosition, { + onDrag, + onDragEnd: o => { + setActive(false); + const newPosition = snap as Mops.PositionModel; + positionProps.setPosition(newPosition); + positionProps.setInitialPosition(newPosition); + hideGuides(); + if (onDragEnd) { + onDragEnd({...o, position: newPosition}); + } + }, + onDragStart: o => { + setActive(true); + if (onDragStart) { + onDragStart(o); + } + } + }); + const [size, sizeProps] = useSize(initialSize, { + centered: altKey, + deg: rotate, + dir, + initialPosition: positionProps.initialPosition, + onResize, + onResizeEnd: o => { + setActive(false); + const newPosition = snap as Mops.PositionModel; + positionProps.setPosition(newPosition); + positionProps.setInitialPosition(newPosition); + hideGuides(); + if (onResizeEnd) { + onResizeEnd({...o, position: newPosition}); + } + }, + onResizeStart: o => { + setActive(true); + if (onResizeStart) { + onResizeStart(o); + } + }, + setInitialPosition: positionProps.setInitialPosition, + setPosition: positionProps.setPosition + }); + const snap = useSnap( + isActive ? shouldSnap : undefined, + {size, position, rotation: {...initialRotation, z: rotate}}, + {addGuides, guideRequests, guides, hideGuides, removeGuides, showGuides, updateGuide} + ); + + return ( + + {isResizable || isRotatable ? ( +
} + style={{ + bottom: 0, + boxShadow: "0 0 0 1px black", + left: 0, + pointerEvents: "none", + position: "absolute", + right: 0, + top: 0 + }}> + {Object.keys(handleVariations).map(key => ( + } + onMouseDown={event => { + setDir(handleDirs[key]); + if (metaKey && isRotatable) { + rotateProps.onMouseDown(event); + } else if (isResizable) { + sizeProps.onMouseDown(event); + } + }} + onTouchStart={event => { + setDir(handleDirs[key]); + if (metaKey && isRotatable) { + rotateProps.onTouchStart(event); + } else if (isResizable) { + sizeProps.onTouchStart(event); + } + }} + /> + ))} +
+ ) : null} +
+ {children} +
+
+ ); +}; + +Box.defaultProps = {as: "div"} + +export const GuidedBox: React.FunctionComponent = ({children, ...props}) => { + return ( + + {context => ( + + {children} + + )} + + ); +}; diff --git a/packages/react-mops/src/elements.css b/packages/react-mops/src/elements.css index bdbff7d..de101f3 100644 --- a/packages/react-mops/src/elements.css +++ b/packages/react-mops/src/elements.css @@ -182,3 +182,4 @@ --x1: url("./cursors/images/se-rotate.png"); --x2: url("./cursors/images/se-rotate@2x.png"); } + diff --git a/packages/react-mops/src/guides/guides.tsx b/packages/react-mops/src/guides/guides.tsx index 7c457de..14c4eae 100644 --- a/packages/react-mops/src/guides/guides.tsx +++ b/packages/react-mops/src/guides/guides.tsx @@ -24,7 +24,7 @@ export const GuidesProvider: React.FunctionComponent<{ containerSize: Mops.ContainerSize; }> = ({children, guideRequests, containerSize}) => { const [guides, setGuides] = React.useState([]); - const addGuides = guideModels => { + const addGuides = React.useCallback(guideModels => { setGuides(state => { const newGuides = guideModels.filter( ({uuid}) => @@ -38,8 +38,8 @@ export const GuidesProvider: React.FunctionComponent<{ $push: newGuides }); }); - }; - const removeGuides = uuids => { + }, [setGuides]); + const removeGuides = React.useCallback(uuids => { setGuides(state => { return uuids ? update(state, { @@ -56,39 +56,46 @@ export const GuidesProvider: React.FunctionComponent<{ }) : []; }); - }; - const showGuides = uuids => { + }, [setGuides]); + const showGuides = React.useCallback(uuids => { setGuides(state => update( state, (uuids || state.map(({uuid}) => uuid)).reduce((previousValue, currentValue) => { const index = state.findIndex(({uuid}) => uuid === currentValue); - return {...previousValue, [index]: {visible: {$set: true}}}; + return index > -1 + ? {...previousValue, [index]: {visible: {$set: true}}} + : previousValue; }, {}) ) ); - }; - const hideGuides = uuids => { + }, [setGuides]); + const hideGuides = React.useCallback(uuids => { setGuides(state => update( state, (uuids || state.map(({uuid}) => uuid)).reduce((previousValue, currentValue) => { const index = state.findIndex(({uuid}) => uuid === currentValue); - return {...previousValue, [index]: {visible: {$set: false}}}; + return index > -1 + ? {...previousValue, [index]: {visible: {$set: false}}} + : previousValue; }, {}) ) ); - }; + }, [setGuides]); - const updateGuide = partialItem => { - setGuides(state => - update(state, { - [state.findIndex(({uuid}) => uuid === partialItem.uuid)]: { - $merge: partialItem - } - }) - ); - }; + const updateGuide = React.useCallback(partialItem => { + setGuides(state => { + const index = state.findIndex(({uuid}) => uuid === partialItem.uuid); + return index > -1 + ? update(state, { + [index]: { + $merge: partialItem + } + }) + : state; + }); + }, [setGuides]); React.useEffect(() => { if (guideRequests) { const guideModels = guideRequests.map(({uuid, x, y}) => { @@ -121,12 +128,12 @@ export const GuidesProvider: React.FunctionComponent<{ return ( {children} diff --git a/packages/react-mops/src/hooks/handle-hooks.ts b/packages/react-mops/src/hooks/handle-hooks.ts deleted file mode 100644 index a19a98d..0000000 --- a/packages/react-mops/src/hooks/handle-hooks.ts +++ /dev/null @@ -1,101 +0,0 @@ -import React from "react"; -import {Mops} from "../types"; -import {useMouseMove} from "./mouse-event-hooks"; - -/** - * - * @param options - * @param options.setSize - * @param options.setInitialSize - * @param options.setPosition - * @param options.setInitialPosition - * @param options.handleSize - * @param options.handlePosition - * @param options.scale - * @param options.rotation - */ -export const useHandle = ({ - setSize, - setInitialSize, - setPosition, - setInitialPosition, - handleSize, - handlePosition, - scale, - rotation -}: Mops.UseHandleProps) => { - const [isDown, setDown] = useMouseMove( - (position: Mops.PositionModel, altKey: boolean, shiftKey: boolean) => { - const nextSize = handleSize(position, altKey, shiftKey); - setSize(nextSize); - setInitialSize(nextSize); - const nextPosition = handlePosition(position, altKey, shiftKey); - setPosition(nextPosition); - setInitialPosition(nextPosition); - }, - (position: Mops.PositionModel, altKey: boolean, shiftKey: boolean) => { - const nextSize = handleSize(position, altKey, shiftKey); - setSize(nextSize); - const nextPosition = handlePosition(position, altKey, shiftKey); - setPosition(nextPosition); - }, - scale, - rotation - ); - return [isDown, setDown] as [boolean, (e: React.MouseEvent) => void]; -}; -export const useHandles = ({ - setBottomRightDown, - setTopLeftDown, - setBottomLeftDown, - setTopRightDown, - setLeftDown, - setBottomDown, - setRightDown, - setTopDown -}): Mops.HandleProps[] => - React.useMemo(() => { - return [ - { - onMouseDown: setTopDown, - variation: "n" - }, - { - onMouseDown: setRightDown, - variation: "e" - }, - { - onMouseDown: setBottomDown, - variation: "s" - }, - { - onMouseDown: setLeftDown, - variation: "w" - }, - { - onMouseDown: setTopRightDown, - variation: "ne" - }, - { - onMouseDown: setBottomRightDown, - variation: "se" - }, - { - onMouseDown: setBottomLeftDown, - variation: "sw" - }, - { - onMouseDown: setTopLeftDown, - variation: "nw" - } - ]; - }, [ - setBottomRightDown, - setTopLeftDown, - setBottomLeftDown, - setTopRightDown, - setLeftDown, - setBottomDown, - setRightDown, - setTopDown - ]); diff --git a/packages/react-mops/src/hooks/index.ts b/packages/react-mops/src/hooks/index.ts index 4cc224b..f15182f 100644 --- a/packages/react-mops/src/hooks/index.ts +++ b/packages/react-mops/src/hooks/index.ts @@ -1,7 +1,5 @@ -export * from "./handle-hooks"; -export * from "./listen"; -export * from "./mouse-event-hooks"; -export * from "./mouse-hooks"; -export * from "./use-hooks"; -export * from "./with-down-hooks"; -export * from "./with-handle-hooks"; +export * from "./keys"; +export * from "./rotate"; +export * from "./position"; +export * from "./size"; +export * from "./snap"; diff --git a/packages/react-mops/src/hooks/listen.ts b/packages/react-mops/src/hooks/listen.ts deleted file mode 100644 index ba679c5..0000000 --- a/packages/react-mops/src/hooks/listen.ts +++ /dev/null @@ -1,43 +0,0 @@ -import React from "react"; - -export const listenRR = ({ - currentSize, - currentPosition, - currentRotation, - loaded, - isDown, - isTopDown, - isRightDown, - isBottomDown, - isLeftDown, - isTopLeftDown, - isTopRightDown, - isBottomLeftDown, - isBottomRightDown, - handleDrag, - handleResize, - isRotationDown, - handleRotate -}) => - React.useEffect(() => { - if (loaded) { - if (isDown) { - handleDrag(); - } else if ( - [ - isTopDown, - isRightDown, - isBottomDown, - isLeftDown, - isTopLeftDown, - isTopRightDown, - isBottomLeftDown, - isBottomRightDown - ].filter(Boolean).length - ) { - handleResize(); - } else if (isRotationDown) { - handleRotate(); - } - } - }, [currentSize, currentPosition, currentRotation]); diff --git a/packages/react-mops/src/hooks/mouse-event-hooks.ts b/packages/react-mops/src/hooks/mouse-event-hooks.ts deleted file mode 100644 index 755119a..0000000 --- a/packages/react-mops/src/hooks/mouse-event-hooks.ts +++ /dev/null @@ -1,240 +0,0 @@ -import React from "react"; -import {Mops} from "../types"; -import {coordinatesToDeg, degToRad, getBoundingBox, to360, withRotation} from "../utils"; - -/** - * Mousemove hook - * @param {Mops.MouseHandler} onMouseUp - * @param {Mops.MouseHandler} onMouseMove - * @param {number} scale - * @param {Mops.RotationModel} [rotation] - */ -export const useMouseMove = ( - onMouseUp: Mops.MouseHandler, - onMouseMove: Mops.MouseHandler, - scale: number, - rotation?: Mops.RotationModel -) => { - const [isDown, setDown] = React.useState(false); - const [initialPosition, setInitialPosition] = React.useState({ - x: 0, - y: 0 - }); - - const getRotatedPosition = React.useCallback( - (event: MouseEvent): Mops.PositionModel => { - const newPosition = { - x: (event.clientX - initialPosition.x) / scale, - y: (event.clientY - initialPosition.y) / scale - }; - return rotation ? withRotation(newPosition.x, newPosition.y, rotation.z) : newPosition; - }, - [initialPosition, scale, rotation] - ); - - const handleMouseUp = React.useCallback( - (event: MouseEvent) => { - if (isDown) { - event.preventDefault(); - const rotatedPosition = getRotatedPosition(event); - setDown(false); - onMouseUp(rotatedPosition, event.altKey, event.shiftKey, event); - } - }, - [setDown, onMouseUp, getRotatedPosition] - ); - - const handleMouseMove = React.useCallback( - (event: MouseEvent) => { - if (isDown) { - event.preventDefault(); - const rotatedPosition = getRotatedPosition(event); - onMouseMove(rotatedPosition, event.altKey, event.shiftKey, event); - } - }, - [onMouseMove, getRotatedPosition] - ); - - React.useEffect(() => { - document.addEventListener("mouseleave", handleMouseUp); - document.addEventListener("mouseup", handleMouseUp); - return () => { - document.removeEventListener("mouseleave", handleMouseUp); - document.removeEventListener("mouseup", handleMouseUp); - }; - }, [handleMouseUp]); - - React.useEffect(() => { - document.addEventListener("mousemove", handleMouseMove); - return () => { - document.removeEventListener("mousemove", handleMouseMove); - }; - }, [handleMouseMove]); - - const handleDown = React.useCallback( - (event: React.MouseEvent) => { - if ((event.target as HTMLElement).tagName !== "INPUT" && event.button !== 2) { - event.preventDefault(); - setDown(true); - setInitialPosition({x: event.clientX, y: event.clientY}); - } - }, - [setInitialPosition, setDown] - ); - - return [isDown, handleDown] as [boolean, (e: React.MouseEvent) => void]; -}; -/** - * - * @param onMouseUp - * @param onMouseMove - * @param onMouseDown - */ -export const useMouseMoveEvent = ( - onMouseUp: (event: MouseEvent) => void, - onMouseMove: (event: MouseEvent) => void, - onMouseDown: (event: React.MouseEvent) => void -) => { - const [isDown, setDown] = React.useState(false); - const handleMouseUp = React.useCallback( - (event: MouseEvent) => { - if (isDown) { - event.preventDefault(); - setDown(false); - onMouseUp(event); - } - }, - [onMouseUp, setDown] - ); - const handleFocus = React.useCallback(() => { - setDown(false); - }, [setDown]); - const handleMouseMove = React.useCallback( - (event: MouseEvent) => { - if (isDown) { - event.preventDefault(); - onMouseMove(event); - } - }, - [onMouseMove] - ); - - React.useEffect(() => { - window.addEventListener("focus", handleFocus); - window.addEventListener("blur", handleFocus); - return () => { - document.removeEventListener("focus", handleFocus); - document.removeEventListener("blur", handleFocus); - }; - }, [handleFocus]); - - React.useEffect(() => { - document.addEventListener("mouseleave", handleMouseUp); - document.addEventListener("mouseup", handleMouseUp); - return () => { - document.removeEventListener("mouseleave", handleMouseUp); - document.removeEventListener("mouseup", handleMouseUp); - }; - }, [handleMouseUp]); - - React.useEffect(() => { - document.addEventListener("mousemove", handleMouseMove); - return () => { - document.removeEventListener("mousemove", handleMouseMove); - }; - }, [handleMouseMove]); - - const handleDown = React.useCallback( - (event: React.MouseEvent) => { - if ((event.target as HTMLElement).tagName !== "INPUT" && event.button !== 2) { - event.preventDefault(); - setDown(true); - onMouseDown(event); - } - }, - [onMouseDown, setDown] - ); - - return [isDown, handleDown] as [boolean, (e: React.MouseEvent) => void]; -}; -export const useHandleMouseEvent = ({additionalAngle, contentRef, initialRotation, isRotatable}) => - React.useCallback( - (event: React.MouseEvent | MouseEvent, init?: boolean) => { - if ( - !isRotatable || - !contentRef || - !(contentRef as React.RefObject).current - ) { - return false; - } - const {clientX, clientY} = event; - const {left, top, width, height} = (contentRef as React.RefObject< - HTMLElement - >).current.getBoundingClientRect(); - const pointer = {x: clientX - left, y: clientY - top}; - const center = {x: width / 2, y: height / 2}; - const deg = coordinatesToDeg(pointer, center); - const newRotationZ = to360(initialRotation.z + (deg - additionalAngle.z)); - const newRotation = (state: Mops.RotationModel) => ({ - x: state.x, - y: state.y, - z: init - ? to360(initialRotation.z) - : event.shiftKey - ? Math.round(newRotationZ / 15) * 15 - : newRotationZ - }); - return { - deg, - rotation: newRotation - }; - }, - [contentRef, initialRotation, isRotatable] - ); -export const useHandleMouse = ({ - addGuides, - currentRotation, - currentSize, - initialPosition, - shouldSnap, - guideRequests, - guides, - hideGuides, - removeGuides, - showGuides, - updateGuide -}) => - React.useCallback( - ({x, y}) => { - const newPosition = { - x: initialPosition.x + x, - y: initialPosition.y + y - }; - return shouldSnap.reduce( - (model, fn) => ({ - ...(fn( - { - position: newPosition, - rotation: currentRotation, - size: getBoundingBox({ - ...currentSize, - angle: currentRotation.z - }) - }, - { - addGuides, - guideRequests, - guides, - hideGuides, - removeGuides, - showGuides, - updateGuide - }, - model - ) as Mops.SnapHandler) - }), - newPosition - ) as Mops.PositionModel; - }, - [currentSize, currentRotation, initialPosition, shouldSnap, showGuides, hideGuides] - ); diff --git a/packages/react-mops/src/hooks/mouse-hooks.ts b/packages/react-mops/src/hooks/mouse-hooks.ts deleted file mode 100644 index b020271..0000000 --- a/packages/react-mops/src/hooks/mouse-hooks.ts +++ /dev/null @@ -1,76 +0,0 @@ -import React from "react"; - -export const useDrag = ({loaded, isDown, handleDragStart, handleDragEnd}) => - React.useEffect(() => { - if (loaded) { - if (isDown) { - handleDragStart(); - } else { - handleDragEnd(); - } - } - }, [isDown]); -export const useDown = ({ - isRotationDown, - isTopDown, - isRightDown, - isBottomDown, - isLeftDown, - isTopLeftDown, - isTopRightDown, - isBottomLeftDown, - isBottomRightDown, - loaded, - isDown, - handleDragStart, - handleDragEnd, - handleResizeStart, - handleRotateStart, - handleRotateEnd, - handleResizeEnd, - metaKey -}) => { - React.useEffect(() => { - if (loaded) { - if (isDown) { - handleDragStart(); - } else { - handleDragEnd(); - } - } - }, [isDown]); - React.useEffect(() => { - if (loaded) { - if ( - [ - isTopDown, - isRightDown, - isBottomDown, - isLeftDown, - isTopLeftDown, - isTopRightDown, - isBottomLeftDown, - isBottomRightDown - ].filter(Boolean).length - ) { - handleResizeStart(); - } else if (isRotationDown) { - handleRotateStart(); - } else if (metaKey) { - handleRotateEnd(); - } else { - handleResizeEnd(); - } - } - }, [ - isRotationDown, - isTopDown, - isRightDown, - isBottomDown, - isLeftDown, - isTopLeftDown, - isTopRightDown, - isBottomLeftDown, - isBottomRightDown - ]); -}; diff --git a/packages/react-mops/src/hooks/use-hooks.ts b/packages/react-mops/src/hooks/use-hooks.ts index cce1e82..ceac859 100644 --- a/packages/react-mops/src/hooks/use-hooks.ts +++ b/packages/react-mops/src/hooks/use-hooks.ts @@ -1,36 +1,12 @@ import React from "react"; import {rotationClasses} from "../cursors"; +import {isOSX} from "../os"; import {to360} from "../utils"; -const OSX = "OSX"; -const WINDOWS = "WINDOWS"; -const LINUX = "LINUX"; -const UNIX = "UNIX"; -const NODE = "UNIX"; - -const getOS = () => { - if ("navigator" in global) { - if (navigator.appVersion.indexOf("Win") !== -1) { - return WINDOWS; - } - if (navigator.appVersion.indexOf("Mac") !== -1) { - return OSX; - } - if (navigator.appVersion.indexOf("X11") !== -1) { - return UNIX; - } - if (navigator.appVersion.indexOf("Linux") !== -1) { - return LINUX; - } - } - return NODE; -}; - -const isOSX = () => getOS() === OSX; /** * */ -export const useMeta = () => { +export const useMetaOld = () => { const [metaKey, setMetaKey] = React.useState(false); const key = isOSX() ? "Meta" : "Control"; const handleKeyDown = React.useCallback( diff --git a/packages/react-mops/src/hooks/with-down-hooks.ts b/packages/react-mops/src/hooks/with-down-hooks.ts deleted file mode 100644 index 0e8d2d2..0000000 --- a/packages/react-mops/src/hooks/with-down-hooks.ts +++ /dev/null @@ -1,59 +0,0 @@ -import React from "react"; -import {useMouseMove, useMouseMoveEvent} from "./mouse-event-hooks"; - -export const useWithDown = ({ - handleMouse, - handleMouseEvent, - hideGuides, - scale, - setAdditionalAngle, - setInitialPosition, - setInitialRotation, - setPosition, - setRotation -}) => { - const [isDown, setDown] = useMouseMove( - p => { - const withSnapLogic = handleMouse(p); - setPosition(withSnapLogic); - setInitialPosition(withSnapLogic); - hideGuides(); - }, - p => { - const withSnapLogic = handleMouse(p); - setPosition(withSnapLogic); - }, - scale - ); - - const [isRotationDown, handleRotationDown] = useMouseMoveEvent( - (event: MouseEvent) => { - const d = handleMouseEvent(event); - if (d) { - setRotation(d.rotation); - setInitialRotation(d.rotation); - } - }, - (event: MouseEvent) => { - const d = handleMouseEvent(event); - if (d) { - setRotation(d.rotation); - } - }, - (event: React.MouseEvent) => { - const d = handleMouseEvent(event, true); - if (d) { - setRotation(d.rotation); - setInitialRotation(d.rotation); - setAdditionalAngle({x: 0, y: 0, z: d.deg}); - } - } - ); - - return { - handleRotationDown, - isDown, - isRotationDown, - setDown - }; -}; diff --git a/packages/react-mops/src/hooks/with-handle-hooks.ts b/packages/react-mops/src/hooks/with-handle-hooks.ts deleted file mode 100644 index 8dedf23..0000000 --- a/packages/react-mops/src/hooks/with-handle-hooks.ts +++ /dev/null @@ -1,251 +0,0 @@ -import React from "react"; -import {withAlt, withAspectRatio, withRotation} from "../utils"; -import {useHandle} from "./handle-hooks"; - -export const useWithHandle = ( - { - setInitialPosition, - setInitialSize, - setPosition, - setSize, - currentPosition, - currentRotation, - initialPosition, - initialSize, - scale, - contentRef, - isResizable, - minHeight, - minWidth - }, - deps = [] -) => - React.useCallback( - ({handleSize, handlePosition}) => { - return useHandle({ - handlePosition: ({x, y}, altKey, shiftKey) => state => { - if (!isResizable) { - return state; - } - const positionState = handlePosition({x, y}, altKey, shiftKey); - const {limit, ...nextPosition} = - typeof positionState === "function" ? positionState(state) : positionState; - return nextPosition; - // return { - // x: limit.x(nextPosition.x), - // y: limit.y(nextPosition.y) - // }; - }, - handleSize: ({x, y}, altKey, shiftKey) => state => { - if (!isResizable) { - return state; - } - const sizeState = handleSize({x, y}, altKey, shiftKey); - const nextSize = typeof sizeState === "function" ? sizeState(state) : sizeState; - const absSize = { - height: Math.abs(nextSize.height), - width: Math.abs(nextSize.width) - }; - return absSize; - // return { - // height: Math.max(minHeight, absSize.height), - // width: Math.max(minWidth, absSize.width) - // }; - }, - rotation: currentRotation, - scale, - setInitialPosition, - setInitialSize, - setPosition, - setSize - }); - }, - [ - setInitialPosition, - setInitialSize, - setPosition, - setSize, - currentPosition, - currentRotation, - initialPosition, - initialSize, - scale, - contentRef, - isResizable, - minHeight, - minWidth, - ...deps - ] - ); -export const useWithCornerHandle = ({withHandle, currentRotation, initialPosition, initialSize}) => - React.useCallback( - ({getPositionDiff, getSizeDiff, limit}) => - withHandle({ - handlePosition: ({x, y}, altKey, shiftKey) => state => { - if (altKey) { - return {limit, ...state}; - } - const dX = getPositionDiff(x); - const e = withRotation(x, 0, currentRotation.z); - const d = withRotation( - 0, - shiftKey - ? initialSize.height - - withAspectRatio(initialSize.width + dX, initialSize, true) - : y, - currentRotation.z - ); - return { - limit, - x: initialPosition.x - d.x / 2 + e.x / 2, - y: initialPosition.y + d.y / 2 - e.y / 2 - }; - }, - handleSize: ({x, y}, altKey, shiftKey) => { - const d = getSizeDiff({x, y}); - return { - height: shiftKey - ? withAspectRatio( - initialSize.width + withAlt(d.x, altKey), - initialSize, - true - ) - : initialSize.height + withAlt(d.y, altKey), - width: initialSize.width + withAlt(d.x, altKey) - }; - } - }), - [withHandle, currentRotation, initialPosition, initialSize] - ); -export const useHandlesDown = ({ - currentRotation, - initialPosition, - initialSize, - // limitLeft, - // limitRight, - // limitTop, - // limitTopLeft, - // limitBottom, - withCornerHandle, - withHandle -}) => { - const [isTopDown, setTopDown] = withHandle({ - handlePosition: ({x, y}, altKey) => { - const d = withRotation(0, y, currentRotation.z); - return { - // limit: limitTop, - x: initialPosition.x - (altKey ? 0 : d.x / 2), - y: initialPosition.y + (altKey ? 0 : d.y / 2) - }; - }, - handleSize: ({x, y}, altKey, shiftKey) => state => ({ - height: initialSize.height - withAlt(y, altKey), - width: shiftKey - ? withAspectRatio(initialSize.height - withAlt(y, altKey), initialSize) - : state.width - }) - }); - - const [isRightDown, setRightDown] = withHandle({ - handlePosition: ({x, y}, altKey) => { - const d = withRotation(x, 0, currentRotation.z); - return { - // limit: limitRight, - x: initialPosition.x + (altKey ? 0 : d.x / 2), - y: initialPosition.y - (altKey ? 0 : d.y / 2) - }; - }, - handleSize: ({x, y}, altKey, shiftKey) => state => ({ - height: shiftKey - ? withAspectRatio(initialSize.width + withAlt(x, altKey), initialSize, true) - : state.height, - width: initialSize.width + withAlt(x, altKey) - }) - }); - - const [isBottomDown, setBottomDown] = withHandle({ - handlePosition: ({x, y}, altKey) => { - const d = withRotation(0, y, currentRotation.z); - return { - // limit: limitBottom, - x: initialPosition.x - (altKey ? 0 : d.x / 2), - y: initialPosition.y + (altKey ? 0 : d.y / 2) - }; - }, - handleSize: ({x, y}, altKey, shiftKey) => state => ({ - height: initialSize.height + withAlt(y, altKey), - width: shiftKey - ? withAspectRatio(initialSize.height + withAlt(y, altKey), initialSize) - : state.width - }) - }); - - const [isLeftDown, setLeftDown] = withHandle({ - handlePosition: ({x, y}, altKey) => { - const d = withRotation(x, 0, currentRotation.z); - return { - // limit: limitLeft, - x: initialPosition.x + (altKey ? 0 : d.x / 2), - y: initialPosition.y - (altKey ? 0 : d.y / 2) - }; - }, - handleSize: ({x, y}, altKey, shiftKey) => state => ({ - height: shiftKey - ? withAspectRatio(initialSize.width - withAlt(x, altKey), initialSize, true) - : state.height, - width: initialSize.width - withAlt(x, altKey) - }) - }); - - const [isTopLeftDown, setTopLeftDown] = withCornerHandle({ - getPositionDiff: x => -x, - getSizeDiff: ({x, y}) => ({x: -x, y: -y}) - // limit: limitTopLeft - }); - - const [isTopRightDown, setTopRightDown] = withCornerHandle({ - getPositionDiff: x => x, - getSizeDiff: ({x, y}) => ({x, y: -y}) - // limit: { - // x: limitLeft.x, - // y: limitTop.y - // } - }); - - const [isBottomLeftDown, setBottomLeftDown] = withCornerHandle({ - getPositionDiff: x => x, - getSizeDiff: ({x, y}) => ({x: -x, y}) - // limit: { - // x: limitLeft.x, - // y: limitBottom.y - // } - }); - - const [isBottomRightDown, setBottomRightDown] = withCornerHandle({ - getPositionDiff: x => -x, - getSizeDiff: ({x, y}) => ({x, y}) - // limit: { - // x: limitRight.x, - // y: limitBottom.y - // } - }); - - return { - isBottomDown, - isBottomLeftDown, - isBottomRightDown, - isLeftDown, - isRightDown, - isTopDown, - isTopLeftDown, - isTopRightDown, - setBottomDown, - setBottomLeftDown, - setBottomRightDown, - setLeftDown, - setRightDown, - setTopDown, - setTopLeftDown, - setTopRightDown - }; -}; diff --git a/packages/react-mops/src/index.ts b/packages/react-mops/src/index.ts index 63ca816..fff2c9e 100644 --- a/packages/react-mops/src/index.ts +++ b/packages/react-mops/src/index.ts @@ -1,6 +1,8 @@ -export * from "./box"; +// export * from "./box"; +// export * from "./guided-box"; export * from "./cursors"; -export * from "./guided-box"; +export * from "./box"; export * from "./guides"; +export * from "./hooks"; export * from "./types"; export * from "./utils"; diff --git a/packages/react-mops/src/types.ts b/packages/react-mops/src/types.ts index a65844a..6d7000c 100644 --- a/packages/react-mops/src/types.ts +++ b/packages/react-mops/src/types.ts @@ -96,7 +96,7 @@ export namespace Mops { * @param {BoundingBox} boundingBox * */ - export type EventHandler = (boundingBox: BoundingBox) => void; + export type EventHandler = (boundingBox: Partial) => void; /** * @typedef BoxProps From 7a8e9e008ac150614e79bc7a1843d4b4cc42b87f Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:19:38 +0200 Subject: [PATCH 12/23] docs: update demo and docs --- README.md | 5 --- docs/index.html | 5 --- docs/logo.jpg | Bin 54591 -> 0 bytes packages/demo/src/elements.tsx | 6 +-- packages/demo/src/pages/home.tsx | 74 ++++++++++++++++++++++--------- 5 files changed, 56 insertions(+), 34 deletions(-) delete mode 100644 docs/logo.jpg diff --git a/README.md b/README.md index 5f3dd52..1a32902 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ # M.O.P.S **M**odify **O**rientation **P**osition **S**ize -> German >> English: **Mops** >> **Pug** -> it moves, it stretches, it rolls around - -

M.O.P.S. logo

- [![MIT license][license-badge]][license] [![Code of Conduct][coc-badge]][coc] diff --git a/docs/index.html b/docs/index.html index 20543d6..256b2fe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,10 +5,5 @@ React M.O.P.S -rotatable demo -resizable demo -draggable demo -draggable demo -combined demo diff --git a/docs/logo.jpg b/docs/logo.jpg deleted file mode 100644 index 6ccb767266c1109119f4f3cfa7cbb4f9707c607e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54591 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 Nf5fZ*0K5MH|JkpONF4wG diff --git a/packages/demo/src/elements.tsx b/packages/demo/src/elements.tsx index 8a435b8..00f27d3 100644 --- a/packages/demo/src/elements.tsx +++ b/packages/demo/src/elements.tsx @@ -80,8 +80,8 @@ export const Container = styled.div<{withGrid?: {x: number; y: number}; hasBound `} `; export const Inner = styled.div` - background-color: hsl(250, 100%, 50%); - background-image: linear-gradient(180deg, hsla(0, 0%, 0%, 0.1), hsla(0, 0%, 0%, 0.5)); + background-color: hsl(300, 100%, 50%); + background-image: linear-gradient(180deg, hsla(0, 0%, 100%, 0.1), hsla(0, 0%, 0%, 0.1)); color: hsla(0, 0%, 100%, 0.4); display: flex; align-items: center; @@ -89,7 +89,7 @@ export const Inner = styled.div` justify-content: center; height: 100%; width: 100%; - border-radius: 2px; + border-radius: 10px; `; export const StyledMarker = styled.span` position: absolute; diff --git a/packages/demo/src/pages/home.tsx b/packages/demo/src/pages/home.tsx index b5b260c..2198def 100644 --- a/packages/demo/src/pages/home.tsx +++ b/packages/demo/src/pages/home.tsx @@ -21,15 +21,14 @@ import { Examples, Headline, Inner, - InvisibleMarker, - StyledBox, SubHeadline, Title, Wrapper } from "../elements"; + export function Home() { - const [items, setItems] = React.useState([]); + const [items, setItems] = React.useState>([]); const [isDraggable, setDraggable] = React.useState(true); const [isResizable, setResizable] = React.useState(true); const [isRotatable, setRotatable] = React.useState(true); @@ -37,8 +36,8 @@ export function Home() { const [showBox, setBox] = React.useState(false); const [showBoundingBox, setBoundingBox] = React.useState(false); const [hasGrid, setGrid] = React.useState(false); - const [hasBounds, setBounds] = React.useState(true); - const [hasGuides, setGuides] = React.useState(false); + const [hasBounds, setBounds] = React.useState(false); + const [hasGuides, setGuides] = React.useState(true); const [hasSiblings, setSiblings] = React.useState(true); const updateItem = (item: Mops.Sibling) => @@ -49,16 +48,19 @@ export function Home() { $merge: item } }; - return update(state, $spec); + return index > -1 ? update(state, $spec) : state; }); const addItem = () => { + const height = Math.round(Math.random() * 200) + 100; + const width = Math.round(Math.random() * 200) + 100; setItems(state => [ ...state, { + backgroundColor: `hsl(${Math.round(Math.random() * 360)}, 100%, 50%)`, position: { - x: 50, - y: 50 + x: width / 2, + y: height / 2 }, rotation: { x: 0, @@ -66,8 +68,8 @@ export function Home() { z: 0 }, size: { - height: 100, - width: 100 + height, + width }, uuid: uuidV4() } @@ -223,28 +225,58 @@ export function Home() { withGrid={hasGrid ? gridSize : undefined} hasBounds={hasBounds}> - {items.map(({uuid, size, position, rotation}, i) => ( + {items.map(({uuid, size, position, rotation, backgroundColor}) => ( { + // tslint:disable-next-line:no-console + console.log("position:start"); + }} + onDrag={() => { + // tslint:disable-next-line:no-console + console.log("position:move"); + }} onDragEnd={b => { - updateItem({uuid, ...b}); + // tslint:disable-next-line:no-console + console.log("position:end"); + updateItem({uuid, rotation, position, size, ...b}); }} - onResizeEnd={b => { - updateItem({uuid, ...b}); + onRotateStart={() => { + // tslint:disable-next-line:no-console + console.log("rotate:start"); + }} + onRotate={() => { + // tslint:disable-next-line:no-console + console.log("rotate:move"); }} onRotateEnd={b => { - updateItem({uuid, ...b}); + // tslint:disable-next-line:no-console + console.log("rotate:end"); + updateItem({uuid, rotation, position, size, ...b}); + }} + onResizeStart={() => { + // tslint:disable-next-line:no-console + console.log("resize:start"); + }} + onResize={() => { + // tslint:disable-next-line:no-console + console.log("resize:move"); + }} + onResizeEnd={b => { + // tslint:disable-next-line:no-console + console.log("resize:end"); + updateItem({uuid, rotation, position, size, ...b}); }} // minHeight={100} // not implemented // minWidth={100} // not implemented - marker={showMarkers ? undefined : InvisibleMarker} - fullHandles={!showMarkers} + // marker={showMarkers ? undefined : InvisibleMarker} + // fullHandles={!showMarkers} size={size} position={position} rotation={rotation} @@ -253,7 +285,7 @@ export function Home() { hasSiblings && toSiblings(items.filter(item => item.uuid !== uuid)) ].filter(Boolean)}> - + ))} From 6b598f70c9c0ecab5f6d9a7b5e6d74e6ca10b06b Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 00:19:54 +0200 Subject: [PATCH 13/23] chore: update yarn.lock --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 326c880..92713e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5704,9 +5704,9 @@ error-ex@^1.2.0, error-ex@^1.3.1: is-arrayish "^0.2.1" es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13.0, es-abstract@^1.5.1: - version "1.14.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.14.2.tgz#7ce108fad83068c8783c3cdf62e504e084d8c497" - integrity sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg== + version "1.15.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.15.0.tgz#8884928ec7e40a79e3c9bc812d37d10c8b24cc57" + integrity sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ== dependencies: es-to-primitive "^1.2.0" function-bind "^1.1.1" @@ -5716,8 +5716,8 @@ es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13 is-regex "^1.0.4" object-inspect "^1.6.0" object-keys "^1.1.1" - string.prototype.trimleft "^2.0.0" - string.prototype.trimright "^2.0.0" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" es-to-primitive@^1.2.0: version "1.2.0" @@ -6808,9 +6808,9 @@ handle-thing@^2.0.0: integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== handlebars@^4.1.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.0.tgz#22e1a897c5d83023d39801f35f6b65cf97ed8b25" - integrity sha512-xkRtOt3/3DzTKMOt3xahj2M/EqNhY988T+imYSlMgs5fVhLN2fmKVVj0LtEGmb+3UUYV5Qmm1052Mm3dIQxOvw== + version "4.4.2" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.2.tgz#8810a9821a9d6d52cb2f57d326d6ce7c3dfe741d" + integrity sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg== dependencies: neo-async "^2.6.0" optimist "^0.6.1" @@ -8223,9 +8223,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" - integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" + integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== dependencies: minimist "^1.2.0" @@ -13436,7 +13436,7 @@ string.prototype.trim@^1.1.2: es-abstract "^1.13.0" function-bind "^1.1.1" -string.prototype.trimleft@^2.0.0: +string.prototype.trimleft@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== @@ -13444,7 +13444,7 @@ string.prototype.trimleft@^2.0.0: define-properties "^1.1.3" function-bind "^1.1.1" -string.prototype.trimright@^2.0.0: +string.prototype.trimright@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== From 2ee5f96f77d6d42e7e477921cf41b4d29d6b44c6 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 9 Oct 2019 09:29:40 +0200 Subject: [PATCH 14/23] fix: touch was causeing errors and unwanted scrolling --- packages/react-mops/src/box.tsx | 3 +++ packages/react-mops/src/hooks/offset.ts | 9 +++++---- packages/react-mops/src/hooks/rotation.ts | 18 ++++++++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/react-mops/src/box.tsx b/packages/react-mops/src/box.tsx index 64d55e0..1a0f8ac 100644 --- a/packages/react-mops/src/box.tsx +++ b/packages/react-mops/src/box.tsx @@ -87,6 +87,7 @@ const Handle: React.FunctionComponent<{ margin: position === "outside" ? -30 : -10, pointerEvents: "all", position: "absolute", + touchAction: "none", width: position === "outside" ? 60 : 20, ...handleVariations[variation] }}> @@ -103,6 +104,7 @@ const Marker = () => ( left: "50%", position: "absolute", top: "50%", + touchAction: "none", transform: "translate(-50%,-50%)", width: 10, zIndex: 2 @@ -291,6 +293,7 @@ const Box: React.FunctionComponent = ({
{ const [initialOffset, setInitialOffset] = React.useState<{x: number; y: number}>(initialState); @@ -30,11 +31,11 @@ export const useOffset = ({onDragStart, onDrag, onDragEnd}, initialState = {x: 0 [handleMove] ); const handleUp = React.useCallback( - pointer => { - const coords = { + (pointer?: Mops.PositionModel) => { + const coords = pointer ? { x: pointer.x - initialOffset.x, y: pointer.y - initialOffset.y - }; + } : offset; setOffset(coords); onDragEnd(coords); }, @@ -48,7 +49,7 @@ export const useOffset = ({onDragStart, onDrag, onDragEnd}, initialState = {x: 0 ); const onTouchEnd = React.useCallback( (event: TouchEvent) => { - handleUp({x: event.touches[0].clientX, y: event.touches[0].clientY}); + handleUp(); }, [handleUp] ); diff --git a/packages/react-mops/src/hooks/rotation.ts b/packages/react-mops/src/hooks/rotation.ts index 91751c2..44c9a8e 100644 --- a/packages/react-mops/src/hooks/rotation.ts +++ b/packages/react-mops/src/hooks/rotation.ts @@ -9,6 +9,9 @@ const useCartesianToPolar = (callback, node, deps) => return; } event.preventDefault(); + if (x === undefined || y === undefined) { + return callback(); + } const {top, left, height, width} = node.getBoundingClientRect(); const {deg} = cartesianToPolar({ x: x - left - width / 2, @@ -38,11 +41,11 @@ export const useRotation = ( [onRotate] ); const handleUp = useCartesianToPolar( - newRotation => { - const deg = to360(newRotation - initialRotation); - setRotation(deg); + (newRotation: number) => { + const deg = newRotation !== undefined ? to360(newRotation - initialRotation) : null; + setRotation(deg === null ? rotation : deg); if (onRotateEnd) { - onRotateEnd(deg); + onRotateEnd(deg === null ? rotation : deg); } }, node, @@ -82,12 +85,15 @@ export const useRotation = ( [handleDown] ); const onMouseUp = React.useCallback( - (event: MouseEvent) => handleUp({x: event.clientX, y: event.clientY}, event), + (event: MouseEvent) => { + handleUp({x: event.clientX, y: event.clientY}, event)}, [handleUp] ); const onTouchEnd = React.useCallback( (event: TouchEvent) => - handleUp({x: event.touches[0].clientX, y: event.touches[0].clientY}, event), + { + handleUp(rotation, event) + }, [handleUp] ); const setDown = usePointer({ From f6328359211854ee75703a8133ea9e930ce19294 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Thu, 10 Oct 2019 09:45:06 +0200 Subject: [PATCH 15/23] chore: adjust demo --- packages/demo/src/constants.tsx | 4 ++-- packages/demo/src/pages/home.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/demo/src/constants.tsx b/packages/demo/src/constants.tsx index bdf9c5e..04c74c9 100644 --- a/packages/demo/src/constants.tsx +++ b/packages/demo/src/constants.tsx @@ -1,8 +1,8 @@ import {v4 as uuidV4} from "uuid"; export const containerSize = { - height: 900, - width: 1200 + height: 300, + width: 300 }; export const gridSize = {x: 25, y: 25}; export const fixedGuides = [ diff --git a/packages/demo/src/pages/home.tsx b/packages/demo/src/pages/home.tsx index 2198def..0ab0ca3 100644 --- a/packages/demo/src/pages/home.tsx +++ b/packages/demo/src/pages/home.tsx @@ -26,7 +26,6 @@ import { Wrapper } from "../elements"; - export function Home() { const [items, setItems] = React.useState>([]); const [isDraggable, setDraggable] = React.useState(true); @@ -225,6 +224,7 @@ export function Home() { withGrid={hasGrid ? gridSize : undefined} hasBounds={hasBounds}> + {items.map(({uuid, size, position, rotation, backgroundColor}) => ( Date: Thu, 10 Oct 2019 09:52:21 +0200 Subject: [PATCH 16/23] feat: use control for non macOS systems --- packages/react-mops/src/box.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/react-mops/src/box.tsx b/packages/react-mops/src/box.tsx index 1a0f8ac..dbfa78a 100644 --- a/packages/react-mops/src/box.tsx +++ b/packages/react-mops/src/box.tsx @@ -1,6 +1,17 @@ import React from "react"; import {GuidesConsumer} from "./guides"; -import {Dir, useAlt, useMeta, usePosition, useRotate, useShift, useSize, useSnap} from "./hooks"; +import { + Dir, + useAlt, + useControl, + useMeta, + usePosition, + useRotate, + useShift, + useSize, + useSnap +} from "./hooks"; +import {isOSX} from "./os"; import {Mops} from "./types"; const handleVariations = { @@ -171,6 +182,7 @@ const Box: React.FunctionComponent = ({ const [isActive, setActive] = React.useState(false); const [dir, setDir] = React.useState({x: 0, y: 0}); const metaKey = useMeta(); + const controlKey = useControl(); const altKey = useAlt(); const shiftKey = useShift(); const [rotate, rotateProps] = useRotate(initialRotation.z, { @@ -280,7 +292,7 @@ const Box: React.FunctionComponent = ({ }} onTouchStart={event => { setDir(handleDirs[key]); - if (metaKey && isRotatable) { + if ((isOSX() ? metaKey : controlKey) && isRotatable) { rotateProps.onTouchStart(event); } else if (isResizable) { sizeProps.onTouchStart(event); From f99a08b9e648f4ddb7caf05ad0d25308a4fc2f6a Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Fri, 11 Oct 2019 09:29:35 +0200 Subject: [PATCH 17/23] chore: update dependencies --- packages/demo/package.json | 10 +-- packages/react-mops/package.json | 12 ++-- yarn.lock | 117 +++++++++++++++++++++++-------- 3 files changed, 98 insertions(+), 41 deletions(-) diff --git a/packages/demo/package.json b/packages/demo/package.json index 5872a43..d1585b1 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -27,7 +27,7 @@ "dependencies": { "@ngineer/head": "1.3.1", "@ngineer/server": "1.3.1", - "@types/react": "16.9.4", + "@types/react": "16.9.5", "@types/react-dom": "16.9.1", "@types/react-hot-loader": "4.1.0", "@types/react-router": "5.1.1", @@ -36,9 +36,9 @@ "@types/uuid": "^3.4.5", "html-react-parser": "0.9.1", "immutability-helper": "^3.0.1", - "react": "16.10.1", - "react-dom": "16.10.1", - "react-hot-loader": "4.12.14", + "react": "16.10.2", + "react-dom": "16.10.2", + "react-hot-loader": "4.12.15", "react-mops": "^1.0.0", "react-router": "5.1.2", "react-router-dom": "5.1.2", @@ -49,7 +49,7 @@ "@ngineer/cli": "1.3.1", "@ngineer/configs": "1.3.1", "babel-plugin-transform-assets-import-to-string": "1.2.0", - "concurrently": "4.1.2", + "concurrently": "5.0.0", "file-loader": "4.2.0", "html-webpack-plugin": "4.0.0-beta.8", "serve": "11.2.0", diff --git a/packages/react-mops/package.json b/packages/react-mops/package.json index b3b0442..4ee27c8 100644 --- a/packages/react-mops/package.json +++ b/packages/react-mops/package.json @@ -55,7 +55,7 @@ }, "dependencies": { "@types/classnames": "^2.2.9", - "@types/react": "^16.9.4", + "@types/react": "^16.9.5", "@types/uuid": "^3.4.5", "classnames": "2.2.6", "immutability-helper": "^3.0.1", @@ -67,18 +67,18 @@ "@ngineer/configs": "1.3.1", "@ngineer/testing-utils": "1.3.1", "@types/react-dom": "16.9.1", - "autoprefixer": "9.6.1", + "autoprefixer": "9.6.4", "cssnano": "4.1.10", "postcss-url": "8.0.0", "react": "16.10.1", - "react-dom": "16.10.1", + "react-dom": "16.10.2", "rollup-plugin-postcss": "2.0.3", - "rollup-plugin-url": "2.2.2", + "rollup-plugin-url": "3.0.0", "ts-node": "8.4.1", - "typescript": "3.6.3" + "typescript": "3.6.4" }, "peerDependencies": { - "react": ">=16.9" + "react": ">=16.10" }, "publishConfig": { "access": "public" diff --git a/yarn.lock b/yarn.lock index 92713e4..8fc2664 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2212,7 +2212,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.9.4", "@types/react@^16.9.4": +"@types/react@*": version "16.9.4" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.4.tgz#de8cf5e5e2838659fb78fa93181078469eeb19fc" integrity sha512-ItGNmJvQ0IvWt8rbk5PLdpdQhvBVxAaXI9hDlx7UMd8Ie1iMIuwMNiKeTfmVN517CdplpyXvA22X4zm4jGGZnw== @@ -2228,6 +2228,14 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@16.9.5", "@types/react@^16.9.5": + version "16.9.5" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.5.tgz#079dabd918b19b32118c25fd00a786bb6d0d5e51" + integrity sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -3002,7 +3010,20 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@9.6.1, autoprefixer@^9.5.1, autoprefixer@^9.6.1: +autoprefixer@9.6.4: + version "9.6.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.4.tgz#e6453be47af316b2923eaeaed87860f52ad4b7eb" + integrity sha512-Koz2cJU9dKOxG8P1f8uVaBntOv9lP4yz9ffWvWaicv9gHBPhpQB22nGijwd8gqW9CNT+UdkbQOQNLVI8jN1ZfQ== + dependencies: + browserslist "^4.7.0" + caniuse-lite "^1.0.30000998" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.18" + postcss-value-parser "^4.0.2" + +autoprefixer@^9.5.1, autoprefixer@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47" integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw== @@ -3436,7 +3457,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.1.1, browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6.4, browserslist@^4.6.6: +browserslist@^4.0.0, browserslist@^4.1.1, browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6.4, browserslist@^4.6.6, browserslist@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17" integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA== @@ -3715,6 +3736,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000887, caniuse-lite@^1.0.30000980, can resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000998.tgz#7227a8046841e7d01e156ae7227a504d065f6744" integrity sha512-8Tj5sPZR9kMHeDD9SZXIVr5m9ofufLLCG2Y4QwQrH18GIwG+kCc+zYdlR036ZRkuKjVVetyxeAgGA1xF7XdmzQ== +caniuse-lite@^1.0.30000998: + version "1.0.30000999" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43" + integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg== + capture-stack-trace@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" @@ -4313,6 +4339,21 @@ concurrently@4.1.2: tree-kill "^1.2.1" yargs "^12.0.5" +concurrently@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.0.0.tgz#99c7567d009411fbdc98299d553c4b99a978612c" + integrity sha512-1yDvK8mduTIdxIxV9C60KoiOySUl/lfekpdbI+U5GXaPrgdffEavFa9QZB3vh68oWOpbCC+TuvxXV9YRPMvUrA== + dependencies: + chalk "^2.4.2" + date-fns "^2.0.1" + lodash "^4.17.15" + read-pkg "^4.0.1" + rxjs "^6.5.2" + spawn-command "^0.0.2-1" + supports-color "^4.5.0" + tree-kill "^1.2.1" + yargs "^12.0.5" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -5011,6 +5052,11 @@ date-fns@^1.23.0, date-fns@^1.30.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +date-fns@^2.0.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.1.tgz#b53f9bb65ae6bd9239437035710e01cf383b625e" + integrity sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw== + date-format@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8" @@ -9267,11 +9313,6 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0 dependencies: minimist "0.0.8" -mkpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d" - integrity sha1-67Opd+evHGg65v2hK1Raa6bFhT0= - modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -11351,7 +11392,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== @@ -11392,7 +11433,7 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.4, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.4, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: version "7.0.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233" integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g== @@ -11756,15 +11797,15 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8, rc@~1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@16.10.1: - version "16.10.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.10.1.tgz#479a6511ba34a429273c213cbc2a9ac4d296dac1" - integrity sha512-SmM4ZW0uug0rn95U8uqr52I7UdNf6wdGLeXDmNLfg3y5q5H9eAbdjF5ubQc3bjDyRrvdAB2IKG7X0GzSpnn5Mg== +react-dom@16.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.10.2.tgz#4840bce5409176bc3a1f2bd8cb10b92db452fda6" + integrity sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.16.1" + scheduler "^0.16.2" react-hot-loader@4.12.10: version "4.12.10" @@ -11780,10 +11821,10 @@ react-hot-loader@4.12.10: shallowequal "^1.1.0" source-map "^0.7.3" -react-hot-loader@4.12.14: - version "4.12.14" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.14.tgz#81ca06ffda0b90aad15d6069339f73ed6428340a" - integrity sha512-ecxH4eBvEaJ9onT8vkEmK1FAAJUh1PqzGqds9S3k+GeihSp7nKAp4fOxytO+Ghr491LiBD38jaKyDXYnnpI9pQ== +react-hot-loader@4.12.15: + version "4.12.15" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.15.tgz#6bf3984e52edbdf02ea8952777f53da1b3c68c95" + integrity sha512-sgkN6g+tgPE6xZzD0Ysqll7KUFYJbMX0DrczT5OxD6S7hZlSnmqSC3ceudwCkiDd65ZTtm+Ayk4Y9k5xxCvpOw== dependencies: fast-levenshtein "^2.0.6" global "^4.3.0" @@ -11867,6 +11908,15 @@ react@16.10.1: object-assign "^4.1.1" prop-types "^15.6.2" +react@16.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.10.2.tgz#a5ede5cdd5c536f745173c8da47bda64797a4cf0" + integrity sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -12555,14 +12605,13 @@ rollup-plugin-typescript2@0.22.1: rollup-pluginutils "2.8.1" tslib "1.10.0" -rollup-plugin-url@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-url/-/rollup-plugin-url-2.2.2.tgz#7623d479be360dd4003742a14eb56add2fdd5b5a" - integrity sha512-89cE0yr0UnDZgMfI8aWrBthnOS2ldARAxeF9rRvagQr5Pqrqob74PMNEodlCMdAU+LdykU7dbTEutVKWVhhORA== +rollup-plugin-url@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-url/-/rollup-plugin-url-3.0.0.tgz#38c1540eb02cf29aec2b7fb57ad3877505ed6a75" + integrity sha512-zeSVpWwsePvc6oTw5eyAlkfHhS6ZT9/pNVVgyzbeMTgd6rovxgg0FPE4o86b9Snembc0rCAaTvEsSfZinJMITw== dependencies: mime "^2.4.4" - mkpath "^1.0.0" - rollup-pluginutils "^2.8.1" + rollup-pluginutils "^2.8.2" rollup-pluginutils@2.8.1: version "2.8.1" @@ -12571,7 +12620,7 @@ rollup-pluginutils@2.8.1: dependencies: estree-walker "^0.6.1" -rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1: +rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== @@ -12693,6 +12742,14 @@ scheduler@^0.16.1: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.16.2.tgz#f74cd9d33eff6fc554edfb79864868e4819132c1" + integrity sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -14305,10 +14362,10 @@ typescript@3.5.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@3.6.3: - version "3.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" - integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== +typescript@3.6.4: + version "3.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d" + integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg== uglify-js@3.4.x: version "3.4.10" From bbfb5b6a05b421e5f8c9832d4af307cf0e4e45bf Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Fri, 11 Oct 2019 09:42:34 +0200 Subject: [PATCH 18/23] revert: rollup-plugin-url@3 breaks build --- packages/react-mops/package.json | 2 +- yarn.lock | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-mops/package.json b/packages/react-mops/package.json index 4ee27c8..0a8f43a 100644 --- a/packages/react-mops/package.json +++ b/packages/react-mops/package.json @@ -73,7 +73,7 @@ "react": "16.10.1", "react-dom": "16.10.2", "rollup-plugin-postcss": "2.0.3", - "rollup-plugin-url": "3.0.0", + "rollup-plugin-url": "2.2.2", "ts-node": "8.4.1", "typescript": "3.6.4" }, diff --git a/yarn.lock b/yarn.lock index 8fc2664..9eaa8ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9313,6 +9313,11 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0 dependencies: minimist "0.0.8" +mkpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d" + integrity sha1-67Opd+evHGg65v2hK1Raa6bFhT0= + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -12605,13 +12610,14 @@ rollup-plugin-typescript2@0.22.1: rollup-pluginutils "2.8.1" tslib "1.10.0" -rollup-plugin-url@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-url/-/rollup-plugin-url-3.0.0.tgz#38c1540eb02cf29aec2b7fb57ad3877505ed6a75" - integrity sha512-zeSVpWwsePvc6oTw5eyAlkfHhS6ZT9/pNVVgyzbeMTgd6rovxgg0FPE4o86b9Snembc0rCAaTvEsSfZinJMITw== +rollup-plugin-url@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-url/-/rollup-plugin-url-2.2.2.tgz#7623d479be360dd4003742a14eb56add2fdd5b5a" + integrity sha512-89cE0yr0UnDZgMfI8aWrBthnOS2ldARAxeF9rRvagQr5Pqrqob74PMNEodlCMdAU+LdykU7dbTEutVKWVhhORA== dependencies: mime "^2.4.4" - rollup-pluginutils "^2.8.2" + mkpath "^1.0.0" + rollup-pluginutils "^2.8.1" rollup-pluginutils@2.8.1: version "2.8.1" @@ -12620,7 +12626,7 @@ rollup-pluginutils@2.8.1: dependencies: estree-walker "^0.6.1" -rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: +rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== From ae0b255b7c9cd8ff2298066d6b7c18f6abecb876 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 16 Oct 2019 11:20:06 +0200 Subject: [PATCH 19/23] docs: fix demo (react version mismatch) --- packages/demo/package.json | 6 +-- packages/react-mops/package.json | 11 ++--- yarn.lock | 78 +++++++++++--------------------- 3 files changed, 35 insertions(+), 60 deletions(-) diff --git a/packages/demo/package.json b/packages/demo/package.json index d1585b1..04a88d3 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -27,10 +27,10 @@ "dependencies": { "@ngineer/head": "1.3.1", "@ngineer/server": "1.3.1", - "@types/react": "16.9.5", - "@types/react-dom": "16.9.1", + "@types/react": "16.9.7", + "@types/react-dom": "16.9.2", "@types/react-hot-loader": "4.1.0", - "@types/react-router": "5.1.1", + "@types/react-router": "5.1.2", "@types/react-router-dom": "5.1.0", "@types/styled-components": "4.1.19", "@types/uuid": "^3.4.5", diff --git a/packages/react-mops/package.json b/packages/react-mops/package.json index 0a8f43a..e94d795 100644 --- a/packages/react-mops/package.json +++ b/packages/react-mops/package.json @@ -55,7 +55,7 @@ }, "dependencies": { "@types/classnames": "^2.2.9", - "@types/react": "^16.9.5", + "@types/react": "^16.9.7", "@types/uuid": "^3.4.5", "classnames": "2.2.6", "immutability-helper": "^3.0.1", @@ -66,19 +66,18 @@ "@ngineer/cli": "1.3.1", "@ngineer/configs": "1.3.1", "@ngineer/testing-utils": "1.3.1", - "@types/react-dom": "16.9.1", - "autoprefixer": "9.6.4", + "@types/react-dom": "16.9.2", "cssnano": "4.1.10", "postcss-url": "8.0.0", - "react": "16.10.1", + "react": "16.10.2", "react-dom": "16.10.2", "rollup-plugin-postcss": "2.0.3", - "rollup-plugin-url": "2.2.2", + "rollup-plugin-url": "2.2.4", "ts-node": "8.4.1", "typescript": "3.6.4" }, "peerDependencies": { - "react": ">=16.10" + "react": ">=16.9" }, "publishConfig": { "access": "public" diff --git a/yarn.lock b/yarn.lock index 9eaa8ec..cf9c36c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2148,10 +2148,10 @@ dependencies: "@types/react" "*" -"@types/react-dom@16.9.1": - version "16.9.1" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.1.tgz#79206237cba9532a9f870b1cd5428bef6b66378c" - integrity sha512-1S/akvkKr63qIUWVu5IKYou2P9fHLb/P2VAwyxVV85JGaGZTcUniMiTuIqM3lXFB25ej6h+CYEQ27ERVwi6eGA== +"@types/react-dom@16.9.2": + version "16.9.2" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.2.tgz#90f9e6c161850be1feb31d2f448121be2a4f3b47" + integrity sha512-hgPbBoI1aTSTvZwo8HYw35UaTldW6n2ETLvHAcfcg1FaOuBV3olmyCe5eMpx2WybWMBPv0MdU2t5GOcQhP+3zA== dependencies: "@types/react" "*" @@ -2189,7 +2189,7 @@ "@types/react" "*" "@types/react-router" "*" -"@types/react-router@*", "@types/react-router@5.1.1": +"@types/react-router@*": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.1.tgz#e0b827556abc70da3473d05daf074c839d6852aa" integrity sha512-S7SlFAPb7ZKr6HHMW0kLHGcz8pyJSL0UdM+JtlWthDqKUWwr7E6oPXuHgkofDI8dKCm16slg8K8VCf5pZJquaA== @@ -2205,6 +2205,14 @@ "@types/history" "*" "@types/react" "*" +"@types/react-router@5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.2.tgz#41e5e6aa333a7b9a2bfdac753c04e1ca4b3e0d21" + integrity sha512-euC3SiwDg3NcjFdNmFL8uVuAFTpZJm0WMFUw+4eXMUnxa7M9RGFEG0szt0z+/Zgk4G2k9JBFhaEnY64RBiFmuw== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-test-renderer@16.8.3": version "16.8.3" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.3.tgz#b6ca14d5fe4c742fd6d68ef42d45e3b5c6dd470a" @@ -2228,10 +2236,10 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@16.9.5", "@types/react@^16.9.5": - version "16.9.5" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.5.tgz#079dabd918b19b32118c25fd00a786bb6d0d5e51" - integrity sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA== +"@types/react@16.9.7", "@types/react@^16.9.7": + version "16.9.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.7.tgz#3b50cb38285a79fd325cf185e0d623e3e0dd2893" + integrity sha512-cF/zwVSOa/NQ5ZSLwUmAdeARgJkmu94shnqDO0dUOsp5roeS8SXtYv06uKeQMYgMjnP6dXH5CecxL+GibvqEFg== dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -3010,19 +3018,6 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@9.6.4: - version "9.6.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.4.tgz#e6453be47af316b2923eaeaed87860f52ad4b7eb" - integrity sha512-Koz2cJU9dKOxG8P1f8uVaBntOv9lP4yz9ffWvWaicv9gHBPhpQB22nGijwd8gqW9CNT+UdkbQOQNLVI8jN1ZfQ== - dependencies: - browserslist "^4.7.0" - caniuse-lite "^1.0.30000998" - chalk "^2.4.2" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.18" - postcss-value-parser "^4.0.2" - autoprefixer@^9.5.1, autoprefixer@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47" @@ -3457,7 +3452,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.1.1, browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6.4, browserslist@^4.6.6, browserslist@^4.7.0: +browserslist@^4.0.0, browserslist@^4.1.1, browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6.4, browserslist@^4.6.6: version "4.7.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17" integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA== @@ -3736,11 +3731,6 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000887, caniuse-lite@^1.0.30000980, can resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000998.tgz#7227a8046841e7d01e156ae7227a504d065f6744" integrity sha512-8Tj5sPZR9kMHeDD9SZXIVr5m9ofufLLCG2Y4QwQrH18GIwG+kCc+zYdlR036ZRkuKjVVetyxeAgGA1xF7XdmzQ== -caniuse-lite@^1.0.30000998: - version "1.0.30000999" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43" - integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg== - capture-stack-trace@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" @@ -9313,11 +9303,6 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0 dependencies: minimist "0.0.8" -mkpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-1.0.0.tgz#ebb3a977e7af1c683ae6fda12b545a6ba6c5853d" - integrity sha1-67Opd+evHGg65v2hK1Raa6bFhT0= - modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -11397,7 +11382,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: +postcss-value-parser@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== @@ -11438,7 +11423,7 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.4, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: +postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.4, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: version "7.0.18" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233" integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g== @@ -11904,15 +11889,6 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.16.1" -react@16.10.1: - version "16.10.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.10.1.tgz#967c1e71a2767dfa699e6ba702a00483e3b0573f" - integrity sha512-2bisHwMhxQ3XQz4LiJJwG3360pY965pTl/MRrZYxIBKVj4fOHoDs5aZAkYXGxDRO1Li+SyjTAilQEbOmtQJHzA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - react@16.10.2: version "16.10.2" resolved "https://registry.yarnpkg.com/react/-/react-16.10.2.tgz#a5ede5cdd5c536f745173c8da47bda64797a4cf0" @@ -12610,14 +12586,14 @@ rollup-plugin-typescript2@0.22.1: rollup-pluginutils "2.8.1" tslib "1.10.0" -rollup-plugin-url@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-url/-/rollup-plugin-url-2.2.2.tgz#7623d479be360dd4003742a14eb56add2fdd5b5a" - integrity sha512-89cE0yr0UnDZgMfI8aWrBthnOS2ldARAxeF9rRvagQr5Pqrqob74PMNEodlCMdAU+LdykU7dbTEutVKWVhhORA== +rollup-plugin-url@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/rollup-plugin-url/-/rollup-plugin-url-2.2.4.tgz#36a6dedb709f73647bed7b253b9dcd4f3e781af4" + integrity sha512-vQjMgJj3tYrg6nKYO/Tvc8s1WTqbaLzHXia17358E6vO0Iq4Ih5WcWYRPopLMUx0x63/31+9ezApAL0HFd998w== dependencies: mime "^2.4.4" - mkpath "^1.0.0" - rollup-pluginutils "^2.8.1" + mkdirp "^0.5.1" + rollup-pluginutils "^2.8.2" rollup-pluginutils@2.8.1: version "2.8.1" @@ -12626,7 +12602,7 @@ rollup-pluginutils@2.8.1: dependencies: estree-walker "^0.6.1" -rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1: +rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== From 66fdfd351ee29238bf05876a697334cdc5645d0a Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 30 Oct 2019 15:36:31 +0100 Subject: [PATCH 20/23] chore: prepare for beta adds several changes but excludes unstable features --- packages/react-mops/README.md | 192 +++++----- packages/react-mops/index.d.ts | 1 + packages/react-mops/package.json | 7 +- packages/react-mops/rollup.config.js | 11 +- packages/react-mops/src/box.tsx | 335 ++++++------------ packages/react-mops/src/elements.css | 243 +++++++------ packages/react-mops/src/elements.tsx | 268 ++++++-------- packages/react-mops/src/guides/guides.tsx | 245 ++++++------- packages/react-mops/src/guides/snapping.ts | 110 +++--- packages/react-mops/src/hooks/aspect-ratio.ts | 13 + packages/react-mops/src/hooks/bounding-box.ts | 5 + .../react-mops/src/hooks/event-listener.ts | 36 +- packages/react-mops/src/hooks/globals.ts | 2 + packages/react-mops/src/hooks/index.ts | 4 + packages/react-mops/src/hooks/keys.ts | 72 ++-- packages/react-mops/src/hooks/offset.ts | 12 +- packages/react-mops/src/hooks/position.ts | 2 +- packages/react-mops/src/hooks/rotate.ts | 2 +- packages/react-mops/src/hooks/rotation.ts | 17 +- packages/react-mops/src/hooks/size.ts | 77 ++-- packages/react-mops/src/hooks/snap.ts | 42 ++- packages/react-mops/src/hooks/use-hooks.ts | 152 -------- packages/react-mops/src/hooks/viewport.ts | 39 ++ packages/react-mops/src/types.ts | 207 +++++------ packages/react-mops/src/utils.ts | 32 +- 25 files changed, 1002 insertions(+), 1124 deletions(-) create mode 100644 packages/react-mops/src/hooks/aspect-ratio.ts create mode 100644 packages/react-mops/src/hooks/bounding-box.ts create mode 100644 packages/react-mops/src/hooks/globals.ts delete mode 100644 packages/react-mops/src/hooks/use-hooks.ts create mode 100644 packages/react-mops/src/hooks/viewport.ts diff --git a/packages/react-mops/README.md b/packages/react-mops/README.md index f1c8d0c..a75f45a 100644 --- a/packages/react-mops/README.md +++ b/packages/react-mops/README.md @@ -1,46 +1,12 @@ # M.O.P.S. -**M**odify **O**rientation **P**osition **S**ize - - - -- [Value Proposition](#value-proposition) -- [Features](#features) -- [Installation](#installation) -- [Docs](#docs) - * [Basic Examples](#basic-examples) -- [Demo](#demo) - * [Live](#live) - * [Screen recordings](#screen-recordings) - +**M**odify **O**rientation **P**osition **S**ize ## Value Proposition M.O.P.S aims to provide a component that allows various transformations to an Element as seen in design software like Photoshop, Sketch any many others. -## Features - -**(implemented / planned)** - -* [x] `` component -* [x] Resize - * [x] Alt key: resize left/right, top/bottom or all directions for corners - * [x] Shift key: retain aspect-ratio - * [x] Snapping - * [x] Touch support - * [ ] Keyboard support -* [x] Rotate - * [x] Meta key: activate rotation - * [x] Shift key: rotate in steps of 15 deg - * [x] Snapping - * [x] Touch support - * [ ] Keyboard support -* [x] Drag - * [x] Snapping - * [x] Touch support - * [ ] Keyboard support - ## Installation **NPM** @@ -57,69 +23,135 @@ yarn add react-mops ## Docs -This an extracted component. +### Components -Tests and documentation has not been written **yet**. Please look at the [examples](https://github.com/dekk-app/react-mops/blob/master/packages/demo/src/pages/home.tsx#L99) for now +#### Box -### Basic Examples +### Hooks -```jsx -import {Box} from "react-mops"; +#### useShift `() => boolean;` -const wrapperStyle = { - position: "relative", - height: 500, - width: 500, - boxShadow: "0 0 0 1px black" -}; -const App = () => { - return ( -
- - Resize me! - - - Rotate me! - - - Drag me! - - - I can do it all! - -
- ); -} -``` +listens for the shift key. + +#### useAlt `() => boolean;` + +listens for the alt/option key. + +#### useControl `() => boolean;` + +listens for the control/ctrl key. + +#### useMeta `() => boolean;` + +listens for the meta key. +#### useRotation -## Demo +Hook to modify the orientation of an element. -### Code Sandbox +#### usePosition -https://codesandbox.io/s/react-mops-4cwhx +Hook to modify the position of an element. -### Live +#### useSize -[https://react-mops.netlify.com](https://react-mops.netlify.com) +Hook to modify the size of an element. -### Screen recordings +#### useSnap -**Resizable** +Hook to enable snapping. -![resizable](https://dekk-app.github.io/react-mops/mops_resizable_01.gif) +#### useGuides -**Rotatable** +Hook to draw guides. -![rotatable](https://dekk-app.github.io/react-mops/mops_rotatable_01.gif) +#### useCursors -**Draggable** +Hook to enable custom cursors. (requires css file) -![draggable](https://dekk-app.github.io/react-mops/mops_draggable_01.gif) -![draggable](https://dekk-app.github.io/react-mops/mops_draggable_02.gif) -**Combined** +### Basic usage examples -![combined](https://dekk-app.github.io/react-mops/mops_combined_01.gif) +```jsx +import {Box} from "react-mops"; +import "react-mops/styles.css"; + +const App = () => ( +
+ Resize me! + Rotate me! + Drag me! + + I can do it all! + +
+); +``` + +### Homegrown + +M.O.P.S. provides hooks to build custom components. + +**rotatable.jsx** + +```jsx +import {useRotation, useShift} from "react-mops"; + +const Rotatable = ({children, initialRotation, onRotate, onRotateEnd, onRotateStart, style}) => { + // Use Shift to rotate in steps + const shiftKey = useShift(); + + // Hook options + const options = React.useMemo( + () => ({ + onRotate, + onRotateEnd, + onRotateStart, + step: 45, // If steps are active rotation uses this value + steps: shiftKey // Activates steps + }), + [shiftKey] + ); + // Rotation Hook + const [ + deg, // Current rotation (degree on z-axis) + { + onMouseDown, // Triggers rotation + onTouchStart, // Triggers rotation + ref // Element considered as rotation center + } + ] = useRotation(initialRotation, options); + + // Dynamic element styles + const style = React.useMemo( + () => ({ + ...style, + transform: `rotate3d(0, 0, 1, ${deg}deg)` + }), + [deg] + ); + // Box with content and a dedicated handle to rotate the element + // The content is used to determind the rotation center (not anchor point) + return ( +
+ +
+ {children} +
+
+ ); +}; + +Rotatable.defaultProps = { + style: {}, + initialRotation: 0 +}; + +export default Rotatable; +``` diff --git a/packages/react-mops/index.d.ts b/packages/react-mops/index.d.ts index 842aa4a..fc3d7fe 100644 --- a/packages/react-mops/index.d.ts +++ b/packages/react-mops/index.d.ts @@ -1 +1,2 @@ +// @ts-ignore export * from "./dist/src"; diff --git a/packages/react-mops/package.json b/packages/react-mops/package.json index e94d795..6721b27 100644 --- a/packages/react-mops/package.json +++ b/packages/react-mops/package.json @@ -55,7 +55,7 @@ }, "dependencies": { "@types/classnames": "^2.2.9", - "@types/react": "^16.9.7", + "@types/react": "^16.9.9", "@types/uuid": "^3.4.5", "classnames": "2.2.6", "immutability-helper": "^3.0.1", @@ -72,7 +72,10 @@ "react": "16.10.2", "react-dom": "16.10.2", "rollup-plugin-postcss": "2.0.3", - "rollup-plugin-url": "2.2.4", + "rollup-plugin-json": "4.0.0", + "rollup-plugin-babel": "4.3.3", + "rollup-plugin-url": "3.0.0", + "rollup-plugin-ts": "0.2.0", "ts-node": "8.4.1", "typescript": "3.6.4" }, diff --git a/packages/react-mops/rollup.config.js b/packages/react-mops/rollup.config.js index f481fc2..351cdce 100644 --- a/packages/react-mops/rollup.config.js +++ b/packages/react-mops/rollup.config.js @@ -1,7 +1,10 @@ const path = require("path"); const url = require("rollup-plugin-url"); -const {createBanner, getPlugins} = require("@ngineer/config-rollup/typescript"); +const {createBanner} = require("@ngineer/config-rollup/typescript"); const postcss = require("rollup-plugin-postcss"); +const babel = require("rollup-plugin-babel"); +const {default: ts} = require("rollup-plugin-ts"); +const json = require("rollup-plugin-json"); module.exports = () => { const cwd = process.cwd(); @@ -35,7 +38,11 @@ module.exports = () => { process.env.NODE_ENV === "production" ? "[hash:base64:5]" : "[local]" } }), - ...getPlugins(tsconfig) + json(), + babel(), + ts({ + tsconfig + }) ] } ]; diff --git a/packages/react-mops/src/box.tsx b/packages/react-mops/src/box.tsx index dbfa78a..1458ae7 100644 --- a/packages/react-mops/src/box.tsx +++ b/packages/react-mops/src/box.tsx @@ -1,8 +1,18 @@ import React from "react"; +import { + Axis, + BoundingBox, + Content, + Handle, + handleDirs, + Handles, + handleVariations, + Wrapper +} from "./elements"; import {GuidesConsumer} from "./guides"; import { - Dir, useAlt, + useBoundingBox, useControl, useMeta, usePosition, @@ -14,147 +24,12 @@ import { import {isOSX} from "./os"; import {Mops} from "./types"; -const handleVariations = { - e: { - left: "100%", - top: "50%" - }, - n: { - left: "50%", - top: 0 - }, - ne: { - left: "100%", - top: 0 - }, - nw: { - left: 0, - top: 0 - }, - s: { - left: "50%", - top: "100%" - }, - se: { - left: "100%", - top: "100%" - }, - sw: { - left: 0, - top: "100%" - }, - w: { - left: 0, - top: "50%" - } -}; - -const handleDirs = { - e: { - x: 1, - y: 0 - }, - n: { - x: 0, - y: -1 - }, - ne: { - x: 1, - y: -1 - }, - nw: { - x: -1, - y: -1 - }, - s: { - x: 0, - y: 1 - }, - se: { - x: 1, - y: 1 - }, - sw: { - x: -1, - y: 1 - }, - w: { - x: -1, - y: 0 - } -}; -const Handle: React.FunctionComponent<{ - marker?: React.ClassicElement; - onMouseDown?: React.MouseEventHandler; - onTouchStart?: React.TouchEventHandler; - position: "outside" | "inside"; - variation: string; -}> = ({marker, onMouseDown, onTouchStart, position, variation}) => ( -
- {marker} -
-); - -const Marker = () => ( -
-); - -interface BoxProps { - shouldSnap?: Mops.SnapHandler[]; - marker?: React.ComponentType; - style?: React.CSSProperties; - className?: string; - ref?: React.Ref; - onResize?: Mops.EventHandler; - onResizeStart?: Mops.EventHandler; - onResizeEnd?: Mops.EventHandler; - onRotate?: Mops.EventHandler; - onRotateStart?: Mops.EventHandler; - onRotateEnd?: Mops.EventHandler; - onDrag?: Mops.EventHandler; - onDragStart?: Mops.EventHandler; - onDragEnd?: Mops.EventHandler; - position?: Mops.PositionModel; - rotation?: Mops.RotationModel; - size?: Mops.SizeModel; - fullHandles?: boolean; - drawBox?: boolean; - minHeight?: number; - minWidth?: number; - drawBoundingBox?: boolean; - isDraggable?: boolean; - isResizable?: boolean; - isRotatable?: boolean; - scale?: number; - as?: keyof JSX.IntrinsicElements | React.ComponentType; -} -const Box: React.FunctionComponent = ({ - as: As, +const Box: React.FunctionComponent = ({ + as, children, + drawBoundingBox, + drawBox, + fullHandles, size: initialSize, position: initialPosition, rotation: initialRotation, @@ -169,6 +44,7 @@ const Box: React.FunctionComponent = ({ onResize, isResizable, isDraggable, + marker, isRotatable, shouldSnap = [], addGuides, @@ -179,28 +55,23 @@ const Box: React.FunctionComponent = ({ showGuides, updateGuide }) => { - const [isActive, setActive] = React.useState(false); - const [dir, setDir] = React.useState({x: 0, y: 0}); + const [isActive, setActive] = React.useState(false); + const [dir, setDir] = React.useState({x: 0, y: 0}); const metaKey = useMeta(); const controlKey = useControl(); const altKey = useAlt(); const shiftKey = useShift(); const [rotate, rotateProps] = useRotate(initialRotation.z, { onRotate, - onRotateEnd: o => { - setActive(false); - const newPosition = snap as Mops.PositionModel; - positionProps.setPosition(newPosition); - positionProps.setInitialPosition(newPosition); - hideGuides(); + onRotateEnd: b => { + const update = onEnd(); if (onRotateEnd) { - onRotateEnd({...o, position: newPosition}); + onRotateEnd({...b, ...update}); } }, - onRotateStart: o => { - setActive(true); + onRotateStart: b => { if (onRotateStart) { - onRotateStart(o); + onRotateStart(b); } }, step: 15, @@ -210,18 +81,15 @@ const Box: React.FunctionComponent = ({ onDrag, onDragEnd: o => { setActive(false); - const newPosition = snap as Mops.PositionModel; - positionProps.setPosition(newPosition); - positionProps.setInitialPosition(newPosition); - hideGuides(); + const update = onEnd(); if (onDragEnd) { - onDragEnd({...o, position: newPosition}); + onDragEnd({...o, ...update}); } }, - onDragStart: o => { + onDragStart: b => { setActive(true); if (onDragStart) { - onDragStart(o); + onDragStart(b); } } }); @@ -231,94 +99,115 @@ const Box: React.FunctionComponent = ({ dir, initialPosition: positionProps.initialPosition, onResize, - onResizeEnd: o => { - setActive(false); - const newPosition = snap as Mops.PositionModel; - positionProps.setPosition(newPosition); - positionProps.setInitialPosition(newPosition); - hideGuides(); + onResizeEnd: b => { + const update = onEnd(); if (onResizeEnd) { - onResizeEnd({...o, position: newPosition}); + onResizeEnd({...b, ...update}); } }, - onResizeStart: o => { - setActive(true); + onResizeStart: b => { if (onResizeStart) { - onResizeStart(o); + onResizeStart(b); } }, setInitialPosition: positionProps.setInitialPosition, setPosition: positionProps.setPosition }); + const snap = useSnap( - isActive ? shouldSnap : undefined, + isActive ? shouldSnap : [], {size, position, rotation: {...initialRotation, z: rotate}}, {addGuides, guideRequests, guides, hideGuides, removeGuides, showGuides, updateGuide} ); + const onEnd = React.useCallback( + () => { + const newPosition = snap.position as Mops.PositionModel; + const newSize = snap.size as Mops.SizeModel; + positionProps.setPosition(newPosition); + positionProps.setInitialPosition(newPosition); + sizeProps.setSize(newSize); + sizeProps.setInitialSize(newSize); + hideGuides(); + return { + position: newPosition, + size: newSize + }; + }, + [hideGuides, sizeProps, positionProps] + ); + + const boundingBox = useBoundingBox(snap.size, rotate); + console.log(guides) + return ( - - {isResizable || isRotatable ? ( -
} + }> + - {Object.keys(handleVariations).map(key => ( - } - onMouseDown={event => { - setDir(handleDirs[key]); - if (metaKey && isRotatable) { - rotateProps.onMouseDown(event); - } else if (isResizable) { - sizeProps.onMouseDown(event); - } - }} - onTouchStart={event => { - setDir(handleDirs[key]); - if ((isOSX() ? metaKey : controlKey) && isRotatable) { + {isResizable || isRotatable ? ( + + {handleVariations.map(key => ( + { + setDir(handleDirs[key]); + if ((isOSX() ? metaKey : controlKey) && isRotatable) { + rotateProps.onMouseDown(event); + } else if (isResizable) { + sizeProps.onMouseDown(event); + } + }} + onTouchStart={event => { + setDir(handleDirs[key]); + if (isResizable) { + sizeProps.onTouchStart(event); + } + }} + /> + ))} + + ) : null} + { + if (event.touches.length > 1) { + if (isRotatable) { rotateProps.onTouchStart(event); - } else if (isResizable) { - sizeProps.onTouchStart(event); } - }} - /> - ))} -
- ) : null} -
- {children} -
-
+ } else if (isDraggable) { + positionProps.onTouchStart(event); + } + }}> + {children} + + + + ); }; -Box.defaultProps = {as: "div"} +Box.defaultProps = { + as: "div", + marker: null +}; -export const GuidedBox: React.FunctionComponent = ({children, ...props}) => { +export const GuidedBox: React.FunctionComponent = ({children, ...props}) => { return ( {context => ( diff --git a/packages/react-mops/src/elements.css b/packages/react-mops/src/elements.css index de101f3..03176a9 100644 --- a/packages/react-mops/src/elements.css +++ b/packages/react-mops/src/elements.css @@ -1,185 +1,226 @@ .handles { - bottom: 0; - left: 0; - pointer-events: none; - position: absolute; - right: 0; - top: 0; - z-index: 2; + bottom: 0; + left: 0; + pointer-events: none; + position: absolute; + right: 0; + top: 0; + z-index: 1; } .forceHandle * { - cursor: var(--x1) 9 9, default; - cursor: -webkit-image-set(var(--x1) 1x, var(--x2) 2x) 9 9, default; + cursor: var(--x1) 9 9, default; + cursor: -webkit-image-set(var(--x1) 1x, var(--x2) 2x) 9 9, default; } .drawOutline { - box-shadow: 0 0 0 1px hsla(200, 100%, 25%, 0.75); + box-shadow: 0 0 0 1px hsla(200, 100%, 25%, 0.75); +} + +.axis { + position: absolute; + top: 50%; + left: 50%; +} + +.guidesWrapper { + pointer-events: none; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.guidesInner { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 10; } .boundingBox { - height: 100%; - pointer-events: none; - position: absolute; - width: 100%; + height: 100%; + pointer-events: none; + position: absolute; + width: 100%; } .handleBase { - height: 20px; - width: 20px; - pointer-events: all; - position: absolute; - visibility: visible; - z-index: 2; - cursor: var(--x1) 9 9, default; - cursor: -webkit-image-set(var(--x1) 1x, var(--x2) 2x) 9 9, default; + height: 20px; + width: 20px; + pointer-events: all; + touch-action: none; + user-select: none; + position: absolute; + visibility: visible; + z-index: 0; } .full { - height: 100%; - width: 100%; + height: 100%; + width: 100%; } .e { - right: -10px; - top: 50%; - transform: translateY(-50%); - width: 20px; + cursor: ew-resize; + right: -10px; + top: 50%; + transform: translateY(-50%); + width: 20px; } .n { - height: 20px; - left: 50%; - top: -10px; - transform: translateX(-50%); + cursor: ns-resize; + height: 20px; + left: 50%; + top: -10px; + transform: translateX(-50%); } .ne { - height: 20px; - right: -10px; - top: -10px; - width: 20px; + cursor: nesw-resize; + height: 20px; + right: -10px; + top: -10px; + width: 20px; + z-index: 1; } .nw { - height: 20px; - left: -10px; - top: -10px; - width: 20px; + cursor: nwse-resize; + height: 20px; + left: -10px; + top: -10px; + width: 20px; + z-index: 1; } .s { - bottom: -10px; - height: 20px; - left: 50%; - transform: translateX(-50%); + cursor: ns-resize; + bottom: -10px; + height: 20px; + left: 50%; + transform: translateX(-50%); } .se { - bottom: -10px; - height: 20px; - right: -10px; - width: 20px; + cursor: nwse-resize; + bottom: -10px; + height: 20px; + right: -10px; + width: 20px; + z-index: 1; } .sw { - bottom: -10px; - height: 20px; - left: -10px; - width: 20px; + cursor: nesw-resize; + bottom: -10px; + height: 20px; + left: -10px; + width: 20px; + z-index: 1; } .w { - left: -10px; - top: 50%; - transform: translateY(-50%); - width: 20px; + cursor: ew-resize; + left: -10px; + top: 50%; + transform: translateY(-50%); + width: 20px; +} + +.cursor { + cursor: var(--x1) 9 9, default; + cursor: -webkit-image-set(var(--x1) 1x, var(--x2) 2x) 9 9, default; } .handleMarker { - background: white; - box-shadow: 0 0 0 1px black; - height: 5px; - left: 50%; - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - width: 5px; - pointer-events: none; + background: white; + box-shadow: 0 0 0 1px black; + height: 5px; + left: 50%; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 5px; + pointer-events: none; } .wrapper { - align-content: center; - align-items: center; - display: flex; - justify-content: center; - left: 0; - pointer-events: none; - position: absolute; - top: 0; + align-content: center; + align-items: center; + display: flex; + justify-content: center; + left: 0; + pointer-events: none; + position: absolute; + top: 0; } .content { - max-height: 100%; - max-width: 100%; - pointer-events: all; - z-index: 1; + max-height: 100%; + max-width: 100%; + pointer-events: all; + z-index: 0; } .move { - cursor: move; + cursor: move; + touch-action: none; } -.colResize { - cursor: col-resize; +.weResize { + cursor: col-resize; } .nwseResize { - cursor: nwse-resize; + cursor: nwse-resize; } -.rowResize { - cursor: row-resize; +.nsResize { + cursor: row-resize; } .neswResize { - cursor: nesw-resize; + cursor: nesw-resize; } .eRotate { - --x1: url("./cursors/images/e-rotate.png"); - --x2: url("./cursors/images/e-rotate@2x.png"); + --x1: url("./cursors/images/e-rotate.png"); + --x2: url("./cursors/images/e-rotate@2x.png"); } .nRotate { - --x1: url("./cursors/images/n-rotate.png"); - --x2: url("./cursors/images/n-rotate@2x.png"); + --x1: url("./cursors/images/n-rotate.png"); + --x2: url("./cursors/images/n-rotate@2x.png"); } .neRotate { - --x1: url("./cursors/images/ne-rotate.png"); - --x2: url("./cursors/images/ne-rotate@2x.png"); + --x1: url("./cursors/images/ne-rotate.png"); + --x2: url("./cursors/images/ne-rotate@2x.png"); } .nwRotate { - --x1: url("./cursors/images/nw-rotate.png"); - --x2: url("./cursors/images/nw-rotate@2x.png"); + --x1: url("./cursors/images/nw-rotate.png"); + --x2: url("./cursors/images/nw-rotate@2x.png"); } .wRotate { - --x1: url("./cursors/images/w-rotate.png"); - --x2: url("./cursors/images/w-rotate@2x.png"); + --x1: url("./cursors/images/w-rotate.png"); + --x2: url("./cursors/images/w-rotate@2x.png"); } .swRotate { - --x1: url("./cursors/images/sw-rotate.png"); - --x2: url("./cursors/images/sw-rotate@2x.png"); + --x1: url("./cursors/images/sw-rotate.png"); + --x2: url("./cursors/images/sw-rotate@2x.png"); } .sRotate { - --x1: url("./cursors/images/s-rotate.png"); - --x2: url("./cursors/images/s-rotate@2x.png"); + --x1: url("./cursors/images/s-rotate.png"); + --x2: url("./cursors/images/s-rotate@2x.png"); } .seRotate { - --x1: url("./cursors/images/se-rotate.png"); - --x2: url("./cursors/images/se-rotate@2x.png"); + --x1: url("./cursors/images/se-rotate.png"); + --x2: url("./cursors/images/se-rotate@2x.png"); } - diff --git a/packages/react-mops/src/elements.tsx b/packages/react-mops/src/elements.tsx index 2b49fca..f254b2b 100644 --- a/packages/react-mops/src/elements.tsx +++ b/packages/react-mops/src/elements.tsx @@ -1,196 +1,146 @@ import cx from "classnames"; import React from "react"; -import {resizeClasses, rotationClasses} from "./cursors"; import styles from "./elements.css"; import {Mops} from "./types"; -export const {Provider: PropProvider, Consumer: PropConsumer} = React.createContext< - Mops.ProviderProps ->({ - getCursorSlice: n => n, - handleRotationDown: () => undefined, - isDraggable: false, - isResizable: false, - isRotatable: false, - metaKey: false -}); - -const HandleLogic: React.RefForwardingComponent< - HTMLAnchorElement, - Partial & {cursorSlice: string} -> = React.forwardRef(({children, cursorSlice, ...props}, ref: React.Ref) => { - React.useEffect(() => { - if (cursorSlice) { - document.body.classList.remove(...resizeClasses, ...rotationClasses); - document.body.classList.add(cursorSlice, styles.forceHandle); - } else { - document.body.classList.remove( - ...resizeClasses, - ...rotationClasses, - styles.forceHandle - ); - } - }, [cursorSlice]); - return ( - - {children} - - ); -}); - -const HandleBase: React.RefForwardingComponent< - HTMLAnchorElement, - Mops.HandleProps -> = React.forwardRef( - ( - {children, className, onClick, isMouseDown, onMouseDown, variation, ...props}, - ref: React.Ref - ) => { - const [isDown, setDown] = React.useState(false); - React.useEffect(() => { - const handleMouseUp = () => { - setDown(false); - }; - window.addEventListener("mouseup", handleMouseUp); - window.addEventListener("mouseleave", handleMouseUp); - window.addEventListener("blur", handleMouseUp); - return () => { - window.removeEventListener("mouseup", handleMouseUp); - window.removeEventListener("mouseleave", handleMouseUp); - window.removeEventListener("blur", handleMouseUp); - }; - }, [setDown]); - const handleClick = React.useCallback( - (e: React.MouseEvent) => { - e.preventDefault(); - if (typeof onClick === "function") { - onClick(e); - } - }, - [onClick] - ); - return ( - - {({handleRotationDown, isResizable, isRotatable, getCursorSlice, metaKey}) => { - const cursorSlice = getCursorSlice(Mops.HandleVariations[variation]); - const rotationClassName = rotationClasses[cursorSlice]; - const resizeClassName = resizeClasses[cursorSlice % resizeClasses.length]; +export const handleVariations: Mops.HandleVariation[] = [ + "e", + "n", + "ne", + "nw", + "s", + "se", + "sw", + "w" +]; - return ( - { - setDown(true); - if (metaKey) { - handleRotationDown(e); - } else { - onMouseDown(e); - } - }}> - {children} - - ); - }} - - ); +export const handleDirs: Mops.HandleDirs = { + e: { + x: 1, + y: 0 + }, + n: { + x: 0, + y: -1 + }, + ne: { + x: 1, + y: -1 + }, + nw: { + x: -1, + y: -1 + }, + s: { + x: 0, + y: 1 + }, + se: { + x: 1, + y: 1 + }, + sw: { + x: -1, + y: 1 + }, + w: { + x: -1, + y: 0 } -); - -const HandleMarker: React.FunctionComponent> = ({ - children, - className, - ...props -}) => ( - - {children} - -); +}; export const Handle: React.RefForwardingComponent< - HTMLAnchorElement, - Mops.HandleProps & {variation: Mops.HandleVariation; full?: boolean} -> = React.forwardRef( - ({className, variation, isMouseDown, style, marker: Marker, full, ...props}, ref) => { - return ( - - - - ); - } -); - -Handle.defaultProps = { - marker: HandleMarker -}; + HTMLSpanElement, + Mops.HandleProps +> = React.forwardRef(({fullSize, marker: Marker, onMouseDown, onTouchStart, variation}, ref) => ( + + {Marker ? : null} + +)); -export const Handles: React.FunctionComponent< - React.HTMLAttributes & {draw?: boolean} -> = ({children, className, draw, ...props}) => ( +export const Handles: React.RefForwardingComponent< + HTMLDivElement, + Mops.HandlesProps +> = React.forwardRef(({children, className, drawOutline, ...props}, ref) => (
{children}
-); +)); + +Handle.defaultProps = { + marker: null +}; export const Wrapper: React.RefForwardingComponent< HTMLElement, Mops.WrapperProps -> = React.forwardRef( - ({children, className, isDown, as: As, ...props}, ref: React.Ref) => ( - } - className={cx(className, styles.wrapper)}> +> = React.forwardRef(({children, className, isDown, as: asElement, ...props}, ref) => { + const As = asElement as React.ComponentType; + return ( + {children} + ); +}); + +export const Axis: React.RefForwardingComponent = React.forwardRef( + ({children, className, ...props}, ref) => ( +
+ {children} +
) ); +export const GuidesWrapper: React.RefForwardingComponent< + HTMLDivElement, + Mops.GuidesWrapperProps +> = React.forwardRef(({children, className, ...props}, ref) => ( +
+ {children} +
+)); + +export const GuidesInner: React.RefForwardingComponent< + HTMLDivElement, + Mops.GuidesInnerProps +> = React.forwardRef(({children, className, ...props}, ref) => ( +
+ {children} +
+)); + export const Content: React.RefForwardingComponent< HTMLDivElement, Mops.ContentProps -> = React.forwardRef( - ({children, className, onMouseDown, ...props}, ref: React.Ref) => ( -
} - className={cx(className, styles.content, { - [styles.move]: typeof onMouseDown === "function" - })}> - {children} -
- ) -); +> = React.forwardRef(({children, className, ...props}, ref) => ( +
+ {children} +
+)); export const BoundingBox: React.RefForwardingComponent< HTMLDivElement, - React.HTMLAttributes & {draw?: boolean} -> = React.forwardRef(({children, className, draw, ...props}, ref: React.Ref) => ( + Mops.BoundingBoxProps +> = React.forwardRef(({children, className, drawOutline, ...props}, ref) => (
} + ref={ref} className={cx(className, styles.boundingBox, { - [styles.drawOutline]: draw + [styles.drawOutline]: drawOutline })}> {children}
diff --git a/packages/react-mops/src/guides/guides.tsx b/packages/react-mops/src/guides/guides.tsx index 14c4eae..b7dfec5 100644 --- a/packages/react-mops/src/guides/guides.tsx +++ b/packages/react-mops/src/guides/guides.tsx @@ -1,5 +1,6 @@ import update from "immutability-helper"; import React from "react"; +import {GuidesInner, GuidesWrapper} from "../elements"; import {Mops} from "../types"; const {Provider, Consumer} = React.createContext({ @@ -24,78 +25,93 @@ export const GuidesProvider: React.FunctionComponent<{ containerSize: Mops.ContainerSize; }> = ({children, guideRequests, containerSize}) => { const [guides, setGuides] = React.useState([]); - const addGuides = React.useCallback(guideModels => { - setGuides(state => { - const newGuides = guideModels.filter( - ({uuid}) => - !Boolean( - state.find(guide => { - return guide.uuid === uuid; - }) - ) - ); - return update(state, { - $push: newGuides - }); - }); - }, [setGuides]); - const removeGuides = React.useCallback(uuids => { - setGuides(state => { - return uuids - ? update(state, { - $splice: uuids - .map(uuid => { - const index = state.findIndex(guide => guide.uuid === uuid); - if (index >= 0) { - return [index, 1]; - } - return false; + const addGuides = React.useCallback( + guideModels => { + setGuides(state => { + const newGuides = guideModels.filter( + ({uuid}) => + !Boolean( + state.find(guide => { + return guide.uuid === uuid; }) - .filter(Boolean) - .sort(([a], [b]) => b - a) - }) - : []; - }); - }, [setGuides]); - const showGuides = React.useCallback(uuids => { - setGuides(state => - update( - state, - (uuids || state.map(({uuid}) => uuid)).reduce((previousValue, currentValue) => { - const index = state.findIndex(({uuid}) => uuid === currentValue); - return index > -1 - ? {...previousValue, [index]: {visible: {$set: true}}} - : previousValue; - }, {}) - ) - ); - }, [setGuides]); - const hideGuides = React.useCallback(uuids => { - setGuides(state => - update( - state, - (uuids || state.map(({uuid}) => uuid)).reduce((previousValue, currentValue) => { - const index = state.findIndex(({uuid}) => uuid === currentValue); - return index > -1 - ? {...previousValue, [index]: {visible: {$set: false}}} - : previousValue; - }, {}) - ) - ); - }, [setGuides]); + ) + ); + return update(state, { + $push: newGuides + }); + }); + }, + [setGuides] + ); + const removeGuides = React.useCallback( + uuids => { + setGuides(state => { + return uuids + ? update(state, { + $splice: uuids + .map(uuid => { + const index = state.findIndex(guide => guide.uuid === uuid); + if (index >= 0) { + return [index, 1]; + } + return false; + }) + .filter(Boolean) + .sort(([a], [b]) => b - a) + }) + : []; + }); + }, + [setGuides] + ); + const showGuides = React.useCallback( + uuids => { + setGuides(state => + update( + state, + (uuids || state.map(({uuid}) => uuid)).reduce((previousValue, currentValue) => { + const index = state.findIndex(({uuid}) => uuid === currentValue); + return index > -1 + ? {...previousValue, [index]: {visible: {$set: true}}} + : previousValue; + }, {}) + ) + ); + }, + [setGuides] + ); + const hideGuides = React.useCallback( + uuids => { + setGuides(state => + update( + state, + (uuids || state.map(({uuid}) => uuid)).reduce((previousValue, currentValue) => { + const index = state.findIndex(({uuid}) => uuid === currentValue); + return index > -1 + ? {...previousValue, [index]: {visible: {$set: false}}} + : previousValue; + }, {}) + ) + ); + }, + [setGuides] + ); - const updateGuide = React.useCallback(partialItem => { - setGuides(state => { - const index = state.findIndex(({uuid}) => uuid === partialItem.uuid); - return index > -1 - ? update(state, { - [index]: { - $merge: partialItem - } - }) - : state; - }); - }, [setGuides]); + const updateGuide = React.useCallback( + partialItem => { + setGuides(state => { + const index = state.findIndex(({uuid}) => uuid === partialItem.uuid); + return index > -1 + ? update(state, { + [index]: { + $merge: partialItem + } + }) + : state; + }); + }, + [setGuides] + ); React.useEffect(() => { if (guideRequests) { const guideModels = guideRequests.map(({uuid, x, y}) => { @@ -144,61 +160,36 @@ export const GuidesProvider: React.FunctionComponent<{ export const Guides: React.RefForwardingComponent< HTMLDivElement, Mops.GuideProps -> = React.forwardRef(({...props}, ref: React.Ref) => { - const sizeRef = React.useRef(); - - const [height, setHeight] = React.useState(0); - const [width, setWidth] = React.useState(0); - - const viewBox = `0 0 ${width} ${height}`; +> = React.forwardRef( + ({guideColor, showGuides, height, width, ...props}, ref: React.Ref) => { + const sizeRef = React.useRef(); + return ( + + + {({guides}) => { + return ( + + + {guides + .filter(({visible}) => visible || showGuides) + .map(({uuid, visible, ...guide}) => ( + + ))} + + + ); + }} + + + ); + } +); - React.useEffect(() => { - if (sizeRef && sizeRef.current) { - const {clientHeight, clientWidth} = sizeRef.current as HTMLDivElement; - setHeight(clientHeight); - setWidth(clientWidth); - } - }, [ref, setHeight, setWidth]); - return ( -
- - {({guides}) => { - return ( -
- - {guides - .filter(({visible}) => visible) - .map(({uuid, visible, ...guide}) => ( - - ))} - -
- ); - }} -
-
- ); -}); +Guides.defaultProps = { + guideColor: "hsl(50, 100%, 50%)" +}; diff --git a/packages/react-mops/src/guides/snapping.ts b/packages/react-mops/src/guides/snapping.ts index ae71cdb..e97fb02 100644 --- a/packages/react-mops/src/guides/snapping.ts +++ b/packages/react-mops/src/guides/snapping.ts @@ -1,45 +1,56 @@ import {v4 as uuidV4} from "uuid"; import {Mops} from "../types"; -import {degToRad, getBoundingBox, inRange} from "../utils"; +import {getBoundingBox, getInnerBox, inRange} from "../utils"; -export const toBounds = ({top, right, bottom, left}) => ( - {position, size}, +export const toBounds = ({top, right, bottom, left}): Mops.SnapHandler => ( + {position, size, rotation}, {}, - model = position + model = {position, size} ) => { + const boundingBox = getBoundingBox({...size, angle: rotation.z}); const boundaries = { - bottom: bottom - size.height / 2, - left: left + size.width / 2, - right: right - size.width / 2, - top: top + size.height / 2 + bottom: bottom - boundingBox.height / 2, + left: left + boundingBox.width / 2, + right: right - boundingBox.width / 2, + top: top + boundingBox.height / 2 }; - const snap: Mops.PositionModel = { - x: Math.max(boundaries.left, Math.min(boundaries.right, model.x)), - y: Math.max(boundaries.top, Math.min(boundaries.bottom, model.y)) + return { + ...model, + position: { + x: Math.max(boundaries.left, Math.min(boundaries.right, model.position.x)), + y: Math.max(boundaries.top, Math.min(boundaries.bottom, model.position.y)) + } }; - return snap; }; -export const toGrid = ({x = 1, y = 1}) => ({position, size}, {}, model = position) => { +export const toGrid = ({x = 1, y = 1}): Mops.SnapHandler => ( + {position, size, rotation}, + {}, + model = {position, size} +) => { + const boundingBox = getBoundingBox({...size, angle: rotation.z}); const half = { - x: size.width / 2, - y: size.height / 2 + x: boundingBox.width / 2, + y: boundingBox.height / 2 }; const snap = { - b: Math.round((model.y + half.y) / y) * y - half.y, - l: Math.round((model.x - half.x) / x) * x + half.x, - r: Math.round((model.x + half.x) / x) * x - half.x, - t: Math.round((model.y - half.y) / y) * y + half.y + b: Math.round((model.position.y + half.y) / y) * y - half.y, + l: Math.round((model.position.x - half.x) / x) * x + half.x, + r: Math.round((model.position.x + half.x) / x) * x - half.x, + t: Math.round((model.position.y - half.y) / y) * y + half.y }; const diff = { - b: Math.abs(model.y - snap.b), - l: Math.abs(model.x - snap.l), - r: Math.abs(model.x - snap.r), - t: Math.abs(model.y - snap.t) + b: Math.abs(model.position.y - snap.b), + l: Math.abs(model.position.x - snap.l), + r: Math.abs(model.position.x - snap.r), + t: Math.abs(model.position.y - snap.t) }; return { - x: diff.l < diff.r ? snap.l : snap.r, - y: diff.t < diff.b ? snap.t : snap.b + ...model, + position: { + x: diff.l < diff.r ? snap.l : snap.r, + y: diff.t < diff.b ? snap.t : snap.b + } }; }; @@ -50,9 +61,9 @@ export const toGuides = ({ y: GUIDE_THRESHOLD } }): Mops.SnapHandler => ( - {position, size}, + {position, size, rotation}, {guideRequests, guides, showGuides, hideGuides}, - model = position + model = {position, size} ) => { const tX = Math.max(GUIDE_THRESHOLD, thresholdX); const tY = Math.max(GUIDE_THRESHOLD, thresholdY); @@ -80,17 +91,24 @@ export const toGuides = ({ } return snap; }, {}); - return {...model, ...withSnap}; + return {...model, position: {...model.position, ...withSnap}}; }; const SIBLING_X = uuidV4(); const SIBLING_Y = uuidV4(); -export const toSiblings = (siblings: Mops.Sibling[]): Mops.SnapHandler => ( +export const toSiblings = ( + siblings: Mops.Sibling[], + {x: thresholdX = GUIDE_THRESHOLD, y: thresholdY = GUIDE_THRESHOLD}: Mops.PositionModel = { + x: GUIDE_THRESHOLD, + y: GUIDE_THRESHOLD + } +): Mops.SnapHandler => ( {position, size, rotation}, - {addGuides, removeGuides, updateGuide, guides}, - model = position + {addGuides, removeGuides, updateGuide, guides, guideRequests, hideGuides}, + model = {position, size} ) => { + const boundingBox = getBoundingBox({...model.size, angle: rotation.z}); const withBoundingBox = siblings.map(sibling => ({ ...sibling, boundingBox: getBoundingBox({ @@ -108,10 +126,10 @@ export const toSiblings = (siblings: Mops.Sibling[]): Mops.SnapHandler => ( const withSnap = withBoundingBox .map(({uuid, ...item}): Partial & {uuid: string} => ({ uuid, - x: inRange(model.x, item.position.x - 10, item.position.x + 10) + x: inRange(model.position.x, item.position.x - thresholdX, item.position.x + thresholdX) ? item.position.x : undefined, - y: inRange(model.y, item.position.y - 10, item.position.y + 10) + y: inRange(model.position.y, item.position.y - thresholdY, item.position.y + thresholdY) ? item.position.y : undefined })) @@ -131,22 +149,22 @@ export const toSiblings = (siblings: Mops.Sibling[]): Mops.SnapHandler => ( hasX && hadX ? Math.abs( withBoundingBox.find(item => item.uuid === previousValue.x.uuid) - .position.y - model.y + .position.y - model.position.y ) > Math.abs( withBoundingBox.find(item => item.uuid === uuid).position.y - - model.y + model.position.y ) : true; const smallerY = hasY && hadY ? Math.abs( withBoundingBox.find(item => item.uuid === previousValue.y.uuid) - .position.x - model.x + .position.x - model.position.x ) > Math.abs( withBoundingBox.find(item => item.uuid === uuid).position.x - - model.x + model.position.x ) : true; return { @@ -171,10 +189,10 @@ export const toSiblings = (siblings: Mops.Sibling[]): Mops.SnapHandler => ( y: hasSnap.y ? withBoundingBox.find(({uuid}) => uuid === withSnap.y.uuid) : undefined }; if (hasSnap.x) { - const dir = snaplings.x.position.y > model.y ? -1 : 1; + const dir = snaplings.x.position.y > model.position.y ? -1 : 1; const [y1, y2] = [ snaplings.x.position.y - (snaplings.x.boundingBox.height / 2) * dir, - (hasSnap.y ? snaplings.y.position.y : model.y) + (size.height / 2) * dir + (hasSnap.y ? snaplings.y.position.y : model.position.y) + (boundingBox.height / 2) * dir ]; const guide = { uuid: SIBLING_X, @@ -193,10 +211,10 @@ export const toSiblings = (siblings: Mops.Sibling[]): Mops.SnapHandler => ( removeGuides([SIBLING_X]); } if (hasSnap.y) { - const dir = snaplings.y.position.x > model.x ? -1 : 1; + const dir = snaplings.y.position.x > model.position.x ? -1 : 1; const [x1, x2] = [ snaplings.y.position.x - (snaplings.y.boundingBox.width / 2) * dir, - (hasSnap.x ? snaplings.x.position.x : model.x) + (size.width / 2) * dir + (hasSnap.x ? snaplings.x.position.x : model.position.x) + (boundingBox.width / 2) * dir ]; const guide = { uuid: SIBLING_Y, @@ -214,9 +232,11 @@ export const toSiblings = (siblings: Mops.Sibling[]): Mops.SnapHandler => ( } else { removeGuides([SIBLING_Y]); } - const snap: Mops.PositionModel = { - x: hasSnap.x ? withSnap.x.value : model.x, - y: hasSnap.y ? withSnap.y.value : model.y + return { + ...model, + position: { + x: hasSnap.x ? withSnap.x.value : model.position.x, + y: hasSnap.y ? withSnap.y.value : model.position.y + } }; - return snap; }; diff --git a/packages/react-mops/src/hooks/aspect-ratio.ts b/packages/react-mops/src/hooks/aspect-ratio.ts new file mode 100644 index 0000000..7dd47f8 --- /dev/null +++ b/packages/react-mops/src/hooks/aspect-ratio.ts @@ -0,0 +1,13 @@ +import React from "react"; + +export const useAspectRatio = ( + aspectRatio, + {setSize, setInitialSize, setPosition, setInitialPosition} +) => { + React.useEffect(() => { + setSize(({height}) => ({ + height, + width: height * aspectRatio + })); + }); +}; diff --git a/packages/react-mops/src/hooks/bounding-box.ts b/packages/react-mops/src/hooks/bounding-box.ts new file mode 100644 index 0000000..86c6b74 --- /dev/null +++ b/packages/react-mops/src/hooks/bounding-box.ts @@ -0,0 +1,5 @@ +import React from "react"; +import {getBoundingBox} from "../utils"; + +export const useBoundingBox = (size, angle) => + React.useMemo(() => getBoundingBox({...size, angle}), [size, angle]); diff --git a/packages/react-mops/src/hooks/event-listener.ts b/packages/react-mops/src/hooks/event-listener.ts index ec60e09..a312d07 100644 --- a/packages/react-mops/src/hooks/event-listener.ts +++ b/packages/react-mops/src/hooks/event-listener.ts @@ -1,19 +1,43 @@ import React from "react"; -export const useEventListener = (type, listener, context, dependencies) => { +type Context = Window | Document | null; +type UseEventListener = ( + type: string, + listener: EventListener, + context: Context, + dependencies: any[] +) => void; +type UseEventListeners = ( + type: string[], + listener: EventListener, + context: Context, + dependencies: any[] +) => void; + +export const useEventListener: UseEventListener = (type, listener, context, dependencies) => { React.useEffect(() => { - context.addEventListener(type, listener); + if (context !== null) { + context.addEventListener(type, listener, {passive: true, capture: false}); + } return () => { - context.removeEventListener(type, listener); + if (context !== null) { + context.removeEventListener(type, listener); + } }; }, [type, listener, context, ...dependencies]); }; -export const useEventListeners = (types, listener, context, dependencies) => { +export const useEventListeners: UseEventListeners = (types, listener, context, dependencies) => { React.useEffect(() => { - types.map(type => context.addEventListener(type, listener)); + if (context !== null) { + types.map(type => + context.addEventListener(type, listener, {passive: true, capture: false}) + ); + } return () => { - types.map(type => context.removeEventListener(type, listener)); + if (context !== null) { + types.map(type => context.removeEventListener(type, listener)); + } }; }, [types, listener, context, ...dependencies]); }; diff --git a/packages/react-mops/src/hooks/globals.ts b/packages/react-mops/src/hooks/globals.ts new file mode 100644 index 0000000..11d411c --- /dev/null +++ b/packages/react-mops/src/hooks/globals.ts @@ -0,0 +1,2 @@ +export const useWindow = () => ("window" in global ? window : null); +export const useDocument = () => ("window" in global && window.document ? document : null); diff --git a/packages/react-mops/src/hooks/index.ts b/packages/react-mops/src/hooks/index.ts index f15182f..79d7aef 100644 --- a/packages/react-mops/src/hooks/index.ts +++ b/packages/react-mops/src/hooks/index.ts @@ -1,5 +1,9 @@ +export * from "./aspect-ratio"; +export * from "./bounding-box"; +export * from "./event-listener"; export * from "./keys"; export * from "./rotate"; export * from "./position"; export * from "./size"; export * from "./snap"; +export * from "./viewport"; diff --git a/packages/react-mops/src/hooks/keys.ts b/packages/react-mops/src/hooks/keys.ts index 53e735a..d07595a 100644 --- a/packages/react-mops/src/hooks/keys.ts +++ b/packages/react-mops/src/hooks/keys.ts @@ -1,5 +1,6 @@ import React from "react"; import {useEventListener, useEventListeners} from "./event-listener"; +import {useDocument, useWindow} from "./globals"; enum KEYS { Alt = "Alt", @@ -10,44 +11,41 @@ enum KEYS { export const useKey = key => { const [isActive, setActive] = React.useState(false); - if ("window" in global) { - // Activate on keydown - useEventListener( - "keydown", - (event: KeyboardEvent) => { - if (event.key === key) { - setActive(true); - } - }, - document, - [setActive, key] - ); - - // Deactivate on keyup - useEventListener( - "keyup", - (event: KeyboardEvent) => { - if (event.key === key) { - setActive(false); - } - }, - document, - [setActive, key] - ); - - // Deactivate on window blur and focus - useEventListeners( - ["focus", "blur"], - () => { + const win = useWindow(); + const doc = useDocument(); + useEventListener( + "keydown", + (event: KeyboardEvent) => { + if (event.key === key) { + setActive(true); + } + }, + doc, + [setActive, key] + ); + + // Deactivate on keyup + useEventListener( + "keyup", + (event: KeyboardEvent) => { + if (event.key === key) { setActive(false); - }, - window, - [setActive] - ); - - return isActive; - } - return false; + } + }, + doc, + [setActive, key] + ); + + // Deactivate on window blur and focus + useEventListeners( + ["focus", "blur"], + () => { + setActive(false); + }, + win, + [setActive] + ); + return isActive; }; export const useMeta = () => useKey(KEYS.Meta); diff --git a/packages/react-mops/src/hooks/offset.ts b/packages/react-mops/src/hooks/offset.ts index f88ff84..f64ea97 100644 --- a/packages/react-mops/src/hooks/offset.ts +++ b/packages/react-mops/src/hooks/offset.ts @@ -18,24 +18,24 @@ export const useOffset = ({onDragStart, onDrag, onDragEnd}, initialState = {x: 0 ); const onMouseMove = React.useCallback( (event: MouseEvent) => { - event.preventDefault(); handleMove({x: event.clientX, y: event.clientY}); }, [handleMove] ); const onTouchMove = React.useCallback( (event: TouchEvent) => { - event.preventDefault(); handleMove({x: event.touches[0].clientX, y: event.touches[0].clientY}); }, [handleMove] ); const handleUp = React.useCallback( (pointer?: Mops.PositionModel) => { - const coords = pointer ? { - x: pointer.x - initialOffset.x, - y: pointer.y - initialOffset.y - } : offset; + const coords = pointer + ? { + x: pointer.x - initialOffset.x, + y: pointer.y - initialOffset.y + } + : offset; setOffset(coords); onDragEnd(coords); }, diff --git a/packages/react-mops/src/hooks/position.ts b/packages/react-mops/src/hooks/position.ts index 24a6eea..71381c6 100644 --- a/packages/react-mops/src/hooks/position.ts +++ b/packages/react-mops/src/hooks/position.ts @@ -22,7 +22,7 @@ interface Props { export const usePosition = ( initialState: Position = {x: 0, y: 0}, - { onDragEnd, onDragStart, onDrag}: Props + {onDragEnd, onDragStart, onDrag}: Props ) => { const [initialPosition, setInitialPosition] = React.useState(initialState); const [position, setPosition] = React.useState(initialPosition); diff --git a/packages/react-mops/src/hooks/rotate.ts b/packages/react-mops/src/hooks/rotate.ts index bd298b5..126232b 100644 --- a/packages/react-mops/src/hooks/rotate.ts +++ b/packages/react-mops/src/hooks/rotate.ts @@ -14,7 +14,7 @@ interface Props { export const useRotate = ( initialState: number = 0, - { steps, step = 15, onRotateEnd, onRotate, onRotateStart}: Props + {steps, step = 15, onRotateEnd, onRotate, onRotateStart}: Props ) => { const ref = React.useRef(); const [initialAngle, setInitialAngle] = React.useState(initialState); diff --git a/packages/react-mops/src/hooks/rotation.ts b/packages/react-mops/src/hooks/rotation.ts index 44c9a8e..17640b8 100644 --- a/packages/react-mops/src/hooks/rotation.ts +++ b/packages/react-mops/src/hooks/rotation.ts @@ -8,7 +8,6 @@ const useCartesianToPolar = (callback, node, deps) => if (!node) { return; } - event.preventDefault(); if (x === undefined || y === undefined) { return callback(); } @@ -42,10 +41,10 @@ export const useRotation = ( ); const handleUp = useCartesianToPolar( (newRotation: number) => { - const deg = newRotation !== undefined ? to360(newRotation - initialRotation) : null; - setRotation(deg === null ? rotation : deg); + const deg = newRotation !== undefined ? to360(newRotation - initialRotation) : rotation; + setRotation(deg); if (onRotateEnd) { - onRotateEnd(deg === null ? rotation : deg); + onRotateEnd(deg); } }, node, @@ -86,14 +85,14 @@ export const useRotation = ( ); const onMouseUp = React.useCallback( (event: MouseEvent) => { - handleUp({x: event.clientX, y: event.clientY}, event)}, + handleUp({x: event.clientX, y: event.clientY}, event); + }, [handleUp] ); const onTouchEnd = React.useCallback( - (event: TouchEvent) => - { - handleUp(rotation, event) - }, + (event: TouchEvent) => { + handleUp(rotation, event); + }, [handleUp] ); const setDown = usePointer({ diff --git a/packages/react-mops/src/hooks/size.ts b/packages/react-mops/src/hooks/size.ts index 3c55ab2..2a33c9a 100644 --- a/packages/react-mops/src/hooks/size.ts +++ b/packages/react-mops/src/hooks/size.ts @@ -3,74 +3,51 @@ import {Mops} from "../types"; import {withRotation} from "../utils"; import {useOffset} from "./offset"; -interface Position { - y: number; - x: number; -} -export interface Dir { - y: -1 | 1 | 0; - x: -1 | 1 | 0; -} -interface Size { - height: number; - width: number; -} -interface Props { - dir?: Dir; - minHeight?: number; - minWidth?: number; - centered?: boolean; - deg?: number; - initialPosition?: Position; - onResizeEnd?: (o: Partial) => void; - onResizeStart?: (o: Partial) => void; - onResize?: (o: Partial) => void; - setPosition?: React.Dispatch>; - setInitialPosition?: React.Dispatch>; -} -export const useSize = ( - initialState: Size, +export const useSize: Mops.UseSize = ( + initialState, { centered, deg = 0, dir = {x: 1, y: 1}, - minHeight, - minWidth, onResize, onResizeEnd, onResizeStart, initialPosition, setInitialPosition, setPosition - }: Props + } ) => { const [initialSize, setInitialSize] = React.useState(initialState); - const [size, setSize] = React.useState(initialSize); + const [size, setSize] = React.useState(initialSize); const setValues = React.useCallback( - (o, end = false) => { - const r = withRotation(o.x, o.y, deg); + ({x, y}, end = false) => { + const {x: rX, y: rY} = withRotation(x, y, deg); const sizeOffset = { - height: initialSize.height + r.y * dir.y * (centered ? 2 : 1), - width: initialSize.width + r.x * dir.x * (centered ? 2 : 1) + height: initialSize.height + rY * dir.y * (centered ? 2 : 1), + width: initialSize.width + rX * dir.x * (centered ? 2 : 1) }; const newSize = { height: Math.abs(sizeOffset.height), width: Math.abs(sizeOffset.width) }; - const d = withRotation( - centered || dir.x === 0 ? 0 : r.x, - centered || dir.y === 0 ? 0 : r.y, + const {x: dX, y: dY} = withRotation( + centered || dir.x === 0 ? 0 : rX, + centered || dir.y === 0 ? 0 : rY, -deg ); const newPosition = { - x: initialPosition.x + d.x / 2, - y: initialPosition.y + d.y / 2 + x: initialPosition.x + dX / 2, + y: initialPosition.y + dY / 2 }; - setPosition(newPosition); + if (setPosition) { + setPosition(newPosition); + } setSize(newSize); if (end) { setInitialSize(newSize); - setInitialPosition(newPosition); + if (setInitialPosition) { + setInitialPosition(newPosition); + } if (onResizeEnd) { onResizeEnd({ size: newSize @@ -100,14 +77,14 @@ export const useSize = ( ); const handleDrag = React.useCallback( - o => { - setValues(o); + (offset: Mops.PositionModel) => { + setValues(offset); }, [setValues] ); const handleDragEnd = React.useCallback( - o => { - setValues(o, true); + (offset: Mops.PositionModel) => { + setValues(offset, true); }, [setValues] ); @@ -123,11 +100,5 @@ export const useSize = ( onDragEnd: handleDragEnd, onDragStart: handleDragStart }); - return [size, methods] as [ - Size, - { - onMouseDown?: (event: React.MouseEvent) => void; - onTouchStart?: (event: React.TouchEvent) => void; - } - ]; + return [size, {...methods, setSize, setInitialSize}]; }; diff --git a/packages/react-mops/src/hooks/snap.ts b/packages/react-mops/src/hooks/snap.ts index cdd713b..4fdb2cf 100644 --- a/packages/react-mops/src/hooks/snap.ts +++ b/packages/react-mops/src/hooks/snap.ts @@ -1,6 +1,5 @@ import React from "react"; import {Mops} from "../types"; -import {getBoundingBox} from "../utils"; export const useSnap = ( shouldSnap: Mops.SnapHandler[], @@ -9,21 +8,30 @@ export const useSnap = ( ) => React.useMemo( () => - shouldSnap - ? shouldSnap.reduce( - (model, fn) => ({ - ...fn( - { - position, - rotation, - size: getBoundingBox({...size, angle: rotation.z}) - }, - guidesContext, - model - ) - }), - position - ) - : position, + shouldSnap.reduce( + (model, fn) => { + const update = fn( + { + position, + rotation, + size + }, + guidesContext, + model + ); + return { + position: { + ...model.position, + ...update.position + }, + rotation, + size: { + ...model.size, + ...update.size + } + }; + }, + {position, rotation, size} + ), [position, rotation, shouldSnap, size, guidesContext] ); diff --git a/packages/react-mops/src/hooks/use-hooks.ts b/packages/react-mops/src/hooks/use-hooks.ts deleted file mode 100644 index ceac859..0000000 --- a/packages/react-mops/src/hooks/use-hooks.ts +++ /dev/null @@ -1,152 +0,0 @@ -import React from "react"; -import {rotationClasses} from "../cursors"; -import {isOSX} from "../os"; -import {to360} from "../utils"; - -/** - * - */ -export const useMetaOld = () => { - const [metaKey, setMetaKey] = React.useState(false); - const key = isOSX() ? "Meta" : "Control"; - const handleKeyDown = React.useCallback( - (e: KeyboardEvent) => { - if (e.key === key) { - setMetaKey(true); - } - }, - [setMetaKey] - ); - - const handleKeyUp = React.useCallback( - (e: KeyboardEvent) => { - if (e.key === key) { - setMetaKey(false); - } - }, - [setMetaKey] - ); - - const handleFocus = React.useCallback(() => { - setMetaKey(false); - }, [setMetaKey]); - - React.useEffect(() => { - window.addEventListener("focus", handleFocus); - window.addEventListener("blur", handleFocus); - return () => { - window.removeEventListener("focus", handleFocus); - window.removeEventListener("blur", handleFocus); - }; - }, [handleFocus]); - - React.useEffect(() => { - document.addEventListener("keydown", handleKeyDown); - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - }, [handleKeyDown]); - - React.useEffect(() => { - document.addEventListener("keyup", handleKeyUp); - return () => { - document.removeEventListener("keyup", handleKeyUp); - }; - }, [handleKeyUp]); - return metaKey; -}; -export const useCursorSlice = rotation => - React.useCallback( - n => { - return (Math.round(to360(rotation.z) / 45) + n) % rotationClasses.length; - }, - [rotation] - ); -export const useHandler = (handler, {currentSize, currentPosition, currentRotation}) => - React.useCallback( - () => - handler && - handler({position: currentPosition, rotation: currentRotation, size: currentSize}), - [handler, currentSize, currentPosition, currentRotation] - ); - -export const useLoaded = setLoaded => - React.useEffect(() => { - setLoaded(true); - }, [setLoaded]); -export const useInitialSize = ({contentRef, setInitialSize, setSize}) => - React.useEffect(() => { - if (contentRef && contentRef.current) { - const {clientHeight: height, clientWidth: width} = contentRef.current; - setSize({ - height, - width - }); - setInitialSize({ - height, - width - }); - } - }, [setSize, setInitialSize]); -export const useHandlers = ({ - currentPosition, - currentRotation, - currentSize, - onDrag, - onDragStart, - onDragEnd, - onResize, - onResizeStart, - onResizeEnd, - onRotate, - onRotateStart, - onRotateEnd -}) => { - const handleDrag = useHandler(onDrag, {currentPosition, currentRotation, currentSize}); - const handleDragStart = useHandler(onDragStart, { - currentPosition, - currentRotation, - currentSize - }); - const handleDragEnd = useHandler(onDragEnd, { - currentPosition, - currentRotation, - currentSize - }); - - const handleResize = useHandler(onResize, {currentPosition, currentRotation, currentSize}); - const handleResizeStart = useHandler(onResizeStart, { - currentPosition, - currentRotation, - currentSize - }); - const handleResizeEnd = useHandler(onResizeEnd, { - currentPosition, - currentRotation, - currentSize - }); - - const handleRotate = useHandler(onRotate, {currentPosition, currentRotation, currentSize}); - const handleRotateStart = useHandler(onRotateStart, { - currentPosition, - currentRotation, - currentSize - }); - const handleRotateEnd = useHandler(onRotateEnd, { - currentPosition, - currentRotation, - currentSize - }); - - return { - handleDrag, - handleDragEnd, - handleDragStart, - handleResize, - handleResizeEnd, - handleResizeStart, - handleRotate, - handleRotateEnd, - handleRotateStart - }; -}; diff --git a/packages/react-mops/src/hooks/viewport.ts b/packages/react-mops/src/hooks/viewport.ts new file mode 100644 index 0000000..70f134e --- /dev/null +++ b/packages/react-mops/src/hooks/viewport.ts @@ -0,0 +1,39 @@ +import React from "react"; +import {Mops} from "../types"; +import {useEventListener} from "./event-listener"; +import {useWindow} from "./globals"; +interface UseViewportProps { + fallbackSize: Mops.SizeModel; + onResize?: (viewportSize: Mops.SizeModel) => void; +} +type UseViewport = (props: UseViewportProps) => Mops.SizeModel; + +export const useViewport: UseViewport = ({fallbackSize, onResize}) => { + const win = useWindow(); + const [size, setSize] = React.useState(fallbackSize); + + React.useEffect(() => { + const newSize = { + height: win ? win.innerHeight : fallbackSize.height, + width: win ? win.innerWidth : fallbackSize.width + }; + setSize(newSize); + }, [fallbackSize]); + + useEventListener( + "resize", + () => { + const newSize = { + height: win ? win.innerHeight : fallbackSize.height, + width: win ? win.innerWidth : fallbackSize.width + }; + setSize(newSize); + if (onResize) { + onResize(newSize); + } + }, + win, + [fallbackSize, setSize, onResize] + ); + return size; +}; diff --git a/packages/react-mops/src/types.ts b/packages/react-mops/src/types.ts index 6d7000c..60c362d 100644 --- a/packages/react-mops/src/types.ts +++ b/packages/react-mops/src/types.ts @@ -2,17 +2,6 @@ import React from "react"; // tslint:disable-next-line:no-namespace export namespace Mops { - /** - * @typedef InitialSizeModel - * @type {object} - * @property {number|string} height - * @property {number|string} width - */ - export interface InitialSizeModel { - height: number | string; - width: number | string; - } - /** * @typedef SizeModel * @type {object} @@ -49,7 +38,7 @@ export namespace Mops { } /** - * @typedef RotationModel + * @typedef BoundingBox * @type {object} * @property {PositionModel} position * @property {RotationModel} rotation @@ -61,21 +50,10 @@ export namespace Mops { size: SizeModel; } - /** - * @typedef MouseHandler - * @type {function} - * @param {PositionModel} position - * @param {boolean} altKey - * @param {boolean} shiftKey - * @param {boolean} altKey - */ - export type MouseHandler = ( - position: PositionModel, - altKey: boolean, - shiftKey: boolean, - event: MouseEvent - ) => void; - + export interface SnapModel { + position: PositionModel; + size: SizeModel; + } /** * @typedef SnapHandler * @type {function} @@ -87,8 +65,8 @@ export namespace Mops { export type SnapHandler = ( boundingBox: BoundingBox, guideContext: Partial, - model?: PositionModel - ) => Partial; + model?: SnapModel + ) => SnapModel; /** * @typedef EventHandler @@ -101,54 +79,62 @@ export namespace Mops { /** * @typedef BoxProps * @type {object} - * @property {SnapHandler} [shouldSnap] + * @property {keyof JSX.IntrinsicElements | React.ComponentType} [as] + * @property {string} [className] + * @property {boolean} [drawBoundingBox] + * @property {boolean} [drawBox] + * @property {boolean} [fullHandles] + * @property {boolean} [isDraggable] + * @property {boolean} [isResizable] + * @property {boolean} [isRotatable] * @property {React.ComponentType} [marker] + * @property {EventHandler} [onDrag] + * @property {EventHandler} [onDragEnd] + * @property {EventHandler} [onDragStart] * @property {EventHandler} [onResize] - * @property {EventHandler} [onResizeStart] * @property {EventHandler} [onResizeEnd] + * @property {EventHandler} [onResizeStart] * @property {EventHandler} [onRotate] - * @property {EventHandler} [onRotateStart] * @property {EventHandler} [onRotateEnd] - * @property {EventHandler} [onDragStart] - * @property {EventHandler} [onDragEnd] + * @property {EventHandler} [onRotateStart] * @property {PositionModel} [position] * @property {RotationModel} [rotation] - * @property {SizeModel} [size] - * @property {boolean} [isDraggable] - * @property {boolean} [isResizable] - * @property {boolean} [isRotatable] * @property {number} [scale] - * @property {keyof JSX.IntrinsicElements | React.ComponentType} [as] + * @property {SnapHandler} [shouldSnap] + * @property {SizeModel} [size] */ export interface BoxProps { - shouldSnap?: SnapHandler[]; - marker?: React.ComponentType; - style?: React.CSSProperties; + as?: keyof JSX.IntrinsicElements | React.ComponentType; className?: string; - ref?: React.Ref; + drawBoundingBox?: boolean; + drawBox?: boolean; + fullHandles?: boolean; + isDraggable?: boolean; + isResizable?: boolean; + isRotatable?: boolean; + marker?: React.ComponentType; + onDrag?: EventHandler; + onDragEnd?: EventHandler; + onDragStart?: EventHandler; onResize?: EventHandler; - onResizeStart?: EventHandler; onResizeEnd?: EventHandler; + onResizeStart?: EventHandler; onRotate?: EventHandler; - onRotateStart?: EventHandler; onRotateEnd?: EventHandler; - onDrag?: EventHandler; - onDragStart?: EventHandler; - onDragEnd?: EventHandler; + onRotateStart?: EventHandler; position?: PositionModel; + ref?: React.Ref; rotation?: RotationModel; - size?: InitialSizeModel; - fullHandles?: boolean; - drawBox?: boolean; - minHeight?: number; - minWidth?: number; - drawBoundingBox?: boolean; - isDraggable?: boolean; - isResizable?: boolean; - isRotatable?: boolean; scale?: number; - as?: keyof JSX.IntrinsicElements | React.ComponentType; + shouldSnap?: SnapHandler[]; + size?: SizeModel; + style?: React.CSSProperties; + } + export interface Dir { + y: -1 | 1 | 0; + x: -1 | 1 | 0; } + export enum HandleVariations { e, se, @@ -160,19 +146,39 @@ export namespace Mops { ne } export type HandleVariation = "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw"; - export interface HandleProps extends React.HTMLAttributes { - variation: Mops.HandleVariation; - isResizable?: boolean; - isRotatable?: boolean; - isMouseDown?: boolean; - metaKey?: boolean; - ref?: React.Ref; - marker?: React.ComponentType<{style?: React.CSSProperties}>; + export type HandleDirs = { + [key in HandleVariation]: Dir; + }; + export interface HandleProps extends React.HTMLAttributes { + ref?: React.Ref; + marker: React.ComponentType | null; + fullSize?: boolean; + variation: HandleVariation; + } + + export interface AxisProps extends React.HTMLAttributes { + ref?: React.Ref; + } + export interface GuidesInnerProps extends React.HTMLAttributes { + ref?: React.Ref; + } + export interface GuidesWrapperProps extends React.HTMLAttributes { + ref?: React.Ref; + } + + export interface BoundingBoxProps extends React.HTMLAttributes { + drawOutline?: boolean; + ref?: React.Ref; + } + + export interface HandlesProps extends React.HTMLAttributes { + drawOutline?: boolean; + ref?: React.Ref; } export interface WrapperProps extends React.HTMLAttributes { + as?: keyof JSX.IntrinsicElements | React.ComponentType; isDown?: boolean; - as?: keyof JSX.IntrinsicElements | React.ComponentType; ref?: React.Ref; } @@ -180,46 +186,16 @@ export namespace Mops { ref?: React.Ref; } - export interface ProviderProps { - isResizable?: boolean; - isRotatable?: boolean; - isDraggable?: boolean; - getCursorSlice?: (n: number) => number; - handleRotationDown?: (e: React.MouseEvent) => void; - metaKey?: boolean; - } - - export interface UseHandleProps { - setSize: (s: Mops.SizeModel | ((state: Mops.SizeModel) => Mops.SizeModel)) => void; - setInitialSize: (s: Mops.SizeModel | ((state: Mops.SizeModel) => Mops.SizeModel)) => void; - setPosition: ( - p: Mops.PositionModel | ((state: Mops.PositionModel) => Mops.PositionModel) - ) => void; - setInitialPosition: ( - p: Mops.PositionModel | ((state: Mops.PositionModel) => Mops.PositionModel) - ) => void; - handleSize: ( - p: Mops.PositionModel, - altKey: boolean, - shiftKey: boolean - ) => (s: Mops.SizeModel) => Mops.SizeModel; - handlePosition: ( - p: Mops.PositionModel, - altKey: boolean, - shiftKey: boolean - ) => (p: Mops.PositionModel) => Mops.PositionModel; - scale: number; - rotation?: Mops.RotationModel; - contentRef?: React.RefObject; - } - export interface GuideProps { + guideColor?: string; + height: number; showGuides?: boolean; + width: number; } export interface Guide { - visible?: boolean; uuid: string; + visible?: boolean; x1: number; x2: number; y1: number; @@ -250,4 +226,33 @@ export namespace Mops { export interface Sibling extends Partial { uuid: string; } + + export type SetPosition = React.Dispatch>; + export type SetSize = React.Dispatch>; + + export interface UseSizeProps { + dir?: Dir; + minHeight?: number; + minWidth?: number; + centered?: boolean; + deg?: number; + initialPosition?: PositionModel; + onResizeEnd?: (boundingBox: Partial) => void; + onResizeStart?: (boundingBox: Partial) => void; + onResize?: (boundingBox: Partial) => void; + setPosition?: SetPosition; + setInitialPosition?: SetPosition; + } + export type UseSize = ( + initialState: SizeModel, + props: UseSizeProps + ) => [ + SizeModel, + { + onMouseDown?: React.MouseEventHandler; + onTouchStart?: React.TouchEventHandler; + setSize?: SetSize; + setInitialSize?: SetSize; + } + ]; } diff --git a/packages/react-mops/src/utils.ts b/packages/react-mops/src/utils.ts index 079903f..badacbb 100644 --- a/packages/react-mops/src/utils.ts +++ b/packages/react-mops/src/utils.ts @@ -150,9 +150,29 @@ export const getBounds = ({ ? Math.PI - rad : rad ); + const s = sin(deg); + const c = cos(deg); return { - height: sin(deg) * width + cos(deg) * height, - width: sin(deg) * height + cos(deg) * width + height: s * width + c * height, + width: s * height + c * width + }; +}; + +export const getInner = ({ + height, + width, + angle +}: { + height: number; + width: number; + angle: number; +}) => { + const s = sin(angle); + const c = cos(angle); + const p = (1 / (c * c - s * s)); + return { + height: p * (height * c - width * s), + width: p * (width * c - height * s) }; }; @@ -164,6 +184,14 @@ export const getBoundingBox = (m: {height: number; width: number; angle: number} }; }; +export const getInnerBox = (m: {height: number; width: number; angle: number}) => { + const {height, width} = getInner(m); + return { + height: Math.abs(height), + width: Math.abs(width) + }; +}; + export const inRange = (value: number, min: number, max: number) => value >= min && value <= max; const fallback = (...n: number[]) => n[0]; From 4f693ff053bac03d9dc3487da6a98bc041774d66 Mon Sep 17 00:00:00 2001 From: Gregor Adams Date: Wed, 30 Oct 2019 15:41:25 +0100 Subject: [PATCH 21/23] chore update demo --- .editorconfig | 28 +- package.json | 3 +- packages/demo/package.json | 3 +- packages/demo/src/constants.tsx | 4 +- packages/demo/src/elements.tsx | 25 +- packages/demo/src/pages/home.tsx | 128 ++++++--- yarn.lock | 476 +++++++++++++++++++++++++++++-- 7 files changed, 574 insertions(+), 93 deletions(-) diff --git a/.editorconfig b/.editorconfig index 16a03a9..f8f8ef2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,17 +4,29 @@ root = true charset = utf-8 end_of_line = lf indent_size = 4 -indent_style = tab +indent_style = space insert_final_newline = true -max_line_length = 100 +max_line_length = 120 +tab_width = 4 trim_trailing_whitespace = true -# trailing spaces in markdown indicate word wrap -[*.md] -trim_trailing_whitespace = false +[*.css] +indent_size = 2 +tab_width = 2 + +[*.scss] +indent_size = 2 +[{*.ats,*.ts,*.tsx}] +indent_style = tab -# Set spaces indent -[*.{json,yml,pug,html}] +[{*.js,*.cjs,*.mjs,*.jsx}] +indent_style = tab + +[{.analysis_options,*.yml,*.yaml}] indent_size = 2 -indent_style = space +tab_width = 2 + +[{.nycrc,.babelrc,.prettierrc,.stylelintrc,.eslintrc,jest.config,.sytlelintrc,*.json,*.jsb3,*.jsb2,*.bowerrc,*.graphqlconfig}] +indent_size = 2 +tab_width = 2 diff --git a/package.json b/package.json index b659b3c..f71eaec 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "ncu": "ncu - u; lerna exec 'ncu -u'", "prerelease": "lerna run build --scope react-mops", "release": "lerna publish --conventional-commits", - "release:next": "yarn prerelease && lerna publish --canary --preid next --pre-dist-tag next", + "release:beta": "yarn prerelease && lerna publish --dist-tag prerelease --preid beta", + "release:rc": "yarn prerelease && lerna publish --dist-tag prerelease --preid rc", "test": "echo 'No tests specified' exit 0" }, "workspaces": [ diff --git a/packages/demo/package.json b/packages/demo/package.json index 04a88d3..2d3300f 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -27,7 +27,7 @@ "dependencies": { "@ngineer/head": "1.3.1", "@ngineer/server": "1.3.1", - "@types/react": "16.9.7", + "@types/react": "16.9.9", "@types/react-dom": "16.9.2", "@types/react-hot-loader": "4.1.0", "@types/react-router": "5.1.2", @@ -48,6 +48,7 @@ "devDependencies": { "@ngineer/cli": "1.3.1", "@ngineer/configs": "1.3.1", + "babel-loader": "8.0.6", "babel-plugin-transform-assets-import-to-string": "1.2.0", "concurrently": "5.0.0", "file-loader": "4.2.0", diff --git a/packages/demo/src/constants.tsx b/packages/demo/src/constants.tsx index 04c74c9..1871278 100644 --- a/packages/demo/src/constants.tsx +++ b/packages/demo/src/constants.tsx @@ -1,8 +1,8 @@ import {v4 as uuidV4} from "uuid"; export const containerSize = { - height: 300, - width: 300 + height: 900, + width: 1600 }; export const gridSize = {x: 25, y: 25}; export const fixedGuides = [ diff --git a/packages/demo/src/elements.tsx b/packages/demo/src/elements.tsx index 00f27d3..98d237f 100644 --- a/packages/demo/src/elements.tsx +++ b/packages/demo/src/elements.tsx @@ -8,7 +8,6 @@ export const Wrapper = styled.div` padding: 10px; border-radius: 2px; box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.5); - width: ${containerSize.width + 40}px; `; export const Examples = styled.div` position: relative; @@ -17,7 +16,7 @@ export const Examples = styled.div` `; export const Example = styled.div` position: relative; - margin: auto; + margin: auto auto 10rem; `; export const Headline = styled.h2` margin: 0; @@ -68,11 +67,7 @@ export const Container = styled.div<{withGrid?: {x: number; y: number}; hasBound var(--line-color) var(--line-width), transparent var(--line-width) ), - linear-gradient( - 90deg, - var(--line-color) var(--line-width), - transparent var(--line-width) - ); + linear-gradient(90deg, var(--line-color) var(--line-width), transparent var(--line-width)); background-repeat: repeat; background-position: calc(var(--line-width) / -2) calc(var(--line-width) / 2); background-size: ${withGrid.x}px ${withGrid.y}px; @@ -93,13 +88,12 @@ export const Inner = styled.div` `; export const StyledMarker = styled.span` position: absolute; - top: 50%; - left: 50%; - height: 80%; - width: 80%; - transform: translate(-50%, -50%); - border: 3px solid white; - background: hsl(180, 100%, 20%); + top: 7px; + right: 7px; + bottom: 7px; + left: 7px; + border: 1px solid black; + background: white; pointer-events: none; `; export const InvisibleMarker = styled.span` @@ -154,6 +148,9 @@ export const ButtonWrapper = styled.div` &:last-child { border-radius: 0 3px 3px 0; } + &:only-child { + border-radius: 3px; + } } `; diff --git a/packages/demo/src/pages/home.tsx b/packages/demo/src/pages/home.tsx index 0ab0ca3..c92adcd 100644 --- a/packages/demo/src/pages/home.tsx +++ b/packages/demo/src/pages/home.tsx @@ -8,11 +8,12 @@ import { toBounds, toGrid, toGuides, - toSiblings + toSiblings, + useViewport } from "react-mops"; import {ThemeProvider} from "styled-components"; import {v4 as uuidV4} from "uuid"; -import {containerSize, fixedGuides, gridSize} from "../constants"; +import {containerSize as initialContainerSize, fixedGuides as initialFixedGuides, gridSize} from "../constants"; import { Button, ButtonWrapper, @@ -21,6 +22,8 @@ import { Examples, Headline, Inner, + StyledBox, + StyledMarker, SubHeadline, Title, Wrapper @@ -31,13 +34,40 @@ export function Home() { const [isDraggable, setDraggable] = React.useState(true); const [isResizable, setResizable] = React.useState(true); const [isRotatable, setRotatable] = React.useState(true); - const [showMarkers, setMarkers] = React.useState(true); + const [showMarkers, setMarkers] = React.useState(false); const [showBox, setBox] = React.useState(false); const [showBoundingBox, setBoundingBox] = React.useState(false); + const [fullHandles, setFullHandles] = React.useState(true); const [hasGrid, setGrid] = React.useState(false); const [hasBounds, setBounds] = React.useState(false); - const [hasGuides, setGuides] = React.useState(true); - const [hasSiblings, setSiblings] = React.useState(true); + const [hasGuides, setGuides] = React.useState(false); + const [hasSiblings, setSiblings] = React.useState(false); + const [containerSize, setContainerSize] = React.useState(initialContainerSize); + const [fixedGuides, setFixedGuides] = React.useState(initialFixedGuides); + const [guideColor, setGuideColor] = React.useState("#ff00ff"); + + const viewportSize = useViewport({ + fallbackSize: initialContainerSize + }); + + React.useEffect(() => { + const newSize = { + height: Math.min(initialContainerSize.height, viewportSize.height), + width: Math.min(initialContainerSize.width, viewportSize.width) + }; + const newContainerSize = { + height: newSize.height - 100, + width: newSize.width - 40 + }; + const newGuides = [ + {x: newContainerSize.width / 4}, + {x: (newContainerSize.width / 4) * 3}, + {x: newContainerSize.width / 2}, + {y: newContainerSize.height / 2} + ]; + setContainerSize(newContainerSize); + setFixedGuides(state => state.map(({uuid}, i) => ({uuid, ...newGuides[i]}))); + }, [setContainerSize, setFixedGuides, viewportSize]); const updateItem = (item: Mops.Sibling) => setItems(state => { @@ -51,12 +81,12 @@ export function Home() { }); const addItem = () => { - const height = Math.round(Math.random() * 200) + 100; - const width = Math.round(Math.random() * 200) + 100; + const height = Math.round(Math.random() * 5) * gridSize.y + gridSize.y * 2; + const width = Math.round(Math.random() * 5) * gridSize.x + gridSize.x * 2; setItems(state => [ ...state, { - backgroundColor: `hsl(${Math.round(Math.random() * 360)}, 100%, 50%)`, + backgroundColor: `hsla(${Math.round(Math.random() * 360)}, 100%, 50%, 0.2)`, position: { x: width / 2, y: height / 2 @@ -77,25 +107,16 @@ export function Home() { const shouldSnap: Mops.SnapHandler[] = React.useMemo( () => [ - hasGrid ? toGrid(gridSize) : undefined, - hasGuides - ? toGuides({ - threshold: { - x: gridSize.x / 2, - y: gridSize.y / 2 - } - }) - : undefined, - hasBounds - ? toBounds({ - bottom: containerSize.height, - left: 0, - right: containerSize.width, - top: 0 - }) - : undefined + hasGrid && toGrid(gridSize), + hasGuides && + toGuides({ + threshold: { + x: gridSize.x / 2, + y: gridSize.y / 2 + } + }) ], - [hasBounds, hasGrid, hasGuides, isDraggable, isResizable, isRotatable] + [hasGrid, hasGuides] ); React.useEffect(() => { addItem(); @@ -109,12 +130,11 @@ export function Home() { Orientation
  • - Press Cmd / Ctrl) to enable rotation. (OS X / Windows, - Linux) + Press (Cmd ⌘ / Ctrl) to enable rotation. (OS X / Windows, Linux)
  • Hold and drag a handle.
  • - Press Shift to rotate in steps of 15°. + Press Shift ⇧ to rotate in steps of 15°.
Position @@ -125,14 +145,14 @@ export function Home() {
  • Hold and drag a handle.
  • - Press Alt to resize opposite sides. + Press Option / Alt ⌥ to resize proportionally from the center.
  • - Press Shift to retain the aspect-ratio. + Press Shift ⇧ to retain the aspect-ratio.

- Snapping (onDrag) + Snapping + Options + + + + Debug