diff --git a/packages/vx-event/package.json b/packages/vx-event/package.json index 04d22e174..8781393ae 100644 --- a/packages/vx-event/package.json +++ b/packages/vx-event/package.json @@ -5,6 +5,7 @@ "sideEffects": false, "main": "lib/index.js", "module": "esm/index.js", + "types": "lib/index.d.ts", "files": [ "lib", "esm" @@ -30,6 +31,7 @@ "access": "public" }, "dependencies": { + "@types/react": "*", "@vx/point": "0.0.192" } } diff --git a/packages/vx-event/src/getXAndYFromEvent.ts b/packages/vx-event/src/getXAndYFromEvent.ts new file mode 100644 index 000000000..8c95e49d6 --- /dev/null +++ b/packages/vx-event/src/getXAndYFromEvent.ts @@ -0,0 +1,22 @@ +import { EventType } from './types'; +import { isTouchEvent } from './typeGuards'; + +const DEFAULT_POINT = { x: 0, y: 0 }; + +export default function getXAndYFromEvent(event?: EventType) { + if (!event) return { ...DEFAULT_POINT }; + + if (isTouchEvent(event)) { + return event.changedTouches.length > 0 + ? { + x: event.changedTouches[0].clientX, + y: event.changedTouches[0].clientX, + } + : { ...DEFAULT_POINT }; + } + + return { + x: event.clientX, + y: event.clientY, + }; +} diff --git a/packages/vx-event/src/index.js b/packages/vx-event/src/index.ts similarity index 100% rename from packages/vx-event/src/index.js rename to packages/vx-event/src/index.ts diff --git a/packages/vx-event/src/localPoint.js b/packages/vx-event/src/localPoint.js deleted file mode 100644 index b4d36109e..000000000 --- a/packages/vx-event/src/localPoint.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Point } from '@vx/point'; - -export default function localPoint(nodeOrEvent, maybeEvent) { - // called with no args - if (!nodeOrEvent) return; - - let node = nodeOrEvent; - let event = maybeEvent; - - // called with localPoint(event) - if (nodeOrEvent.target) { - event = nodeOrEvent; - - // set node to targets owner svg - node = event.target.ownerSVGElement; - - // find the outermost svg - while (node.ownerSVGElement) { - node = node.ownerSVGElement; - } - } - - // default to mouse event - let { clientX, clientY } = event; - - // support touch event - if (event.changedTouches) { - clientX = event.changedTouches[0].clientX; - clientY = event.changedTouches[0].clientY; - } - - // calculate coordinates from svg - if (node.createSVGPoint) { - let point = node.createSVGPoint(); - point.x = clientX; - point.y = clientY; - point = point.matrixTransform(node.getScreenCTM().inverse()); - return new Point({ - x: point.x, - y: point.y, - }); - } - - // fallback to calculating position from non-svg dom node - const rect = node.getBoundingClientRect(); - return new Point({ - x: clientX - rect.left - node.clientLeft, - y: clientY - rect.top - node.clientTop, - }); -} diff --git a/packages/vx-event/src/localPoint.ts b/packages/vx-event/src/localPoint.ts new file mode 100644 index 000000000..3f497e108 --- /dev/null +++ b/packages/vx-event/src/localPoint.ts @@ -0,0 +1,14 @@ +import localPointGeneric from './localPointGeneric'; +import { EventType } from './types'; +import { isElement, isEvent } from './typeGuards'; + +export default function localPoint(nodeOrEvent: Element | EventType, maybeEvent?: EventType) { + if (isElement(nodeOrEvent) && maybeEvent) { + return localPointGeneric(nodeOrEvent, maybeEvent); + } + if (isEvent(nodeOrEvent)) { + const node = nodeOrEvent.target as Element; + if (node) return localPointGeneric(node, nodeOrEvent); + } + return null; +} diff --git a/packages/vx-event/src/localPointGeneric.ts b/packages/vx-event/src/localPointGeneric.ts new file mode 100644 index 000000000..4138627c3 --- /dev/null +++ b/packages/vx-event/src/localPointGeneric.ts @@ -0,0 +1,34 @@ +import { Point } from '@vx/point'; +import { EventType } from './types'; +import { isSVGElement, isSVGGraphicsElement, isSVGSVGElement } from './typeGuards'; +import getXAndYFromEvent from './getXAndYFromEvent'; + +export default function localPoint(node: Element, event: EventType) { + if (!node || !event) return null; + + const coords = getXAndYFromEvent(event); + + // find top-most SVG + const svg = isSVGElement(node) ? node.ownerSVGElement : node; + const screenCTM = isSVGGraphicsElement(node) ? node.getScreenCTM() : null; + + if (isSVGSVGElement(svg) && screenCTM) { + let point = svg.createSVGPoint(); + point.x = coords.x; + point.y = coords.y; + point = point.matrixTransform(screenCTM.inverse()); + + return new Point({ + x: point.x, + y: point.y, + }); + } + + // fall back to bounding box + const rect = node.getBoundingClientRect(); + + return new Point({ + x: coords.x - rect.left - node.clientLeft, + y: coords.y - rect.top - node.clientTop, + }); +} diff --git a/packages/vx-event/src/touchPoint.js b/packages/vx-event/src/touchPoint.js deleted file mode 100644 index 4aa26ffe3..000000000 --- a/packages/vx-event/src/touchPoint.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Point } from '@vx/point'; - -export default function touchPoint(node, event) { - if (!node) return; - const svg = node.ownerSVGElement || node; - if (svg.createSVGPoint) { - let point = svg.createSVGPoint(); - point.x = event.changedTouches[0].clientX; - point.y = event.changedTouches[0].clientY; - point = point.matrixTransform(node.getScreenCTM().inverse()); - return new Point({ - x: point.x, - y: point.y, - }); - } - const rect = node.getBoundingClientRect(); - return new Point({ - x: event.changedTouches[0].clientX - rect.left - node.clientLeft, - y: event.changedTouches[0].clientY - rect.top - node.clientTop, - }); -} diff --git a/packages/vx-event/src/touchPoint.ts b/packages/vx-event/src/touchPoint.ts new file mode 100644 index 000000000..b392c2c4a --- /dev/null +++ b/packages/vx-event/src/touchPoint.ts @@ -0,0 +1 @@ +export { default } from './localPointGeneric'; diff --git a/packages/vx-event/src/typeGuards.ts b/packages/vx-event/src/typeGuards.ts new file mode 100644 index 000000000..f316e075e --- /dev/null +++ b/packages/vx-event/src/typeGuards.ts @@ -0,0 +1,32 @@ +import { EventType } from './types'; + +export function isElement(elem?: Element | EventType): elem is Element { + return !!elem && elem instanceof Element; +} + +// functional definition of isSVGElement. Note that SVGSVGElements are HTMLElements +export function isSVGElement(elem?: Element): elem is SVGElement { + return !!elem && (elem instanceof SVGElement || 'ownerSVGElement' in elem); +} + +// functional definition of SVGGElement +export function isSVGSVGElement(elem?: Element | null): elem is SVGSVGElement { + return !!elem && 'createSVGPoint' in elem; +} + +export function isSVGGraphicsElement(elem?: Element | null): elem is SVGGraphicsElement { + return !!elem && 'getScreenCTM' in elem; +} + +// functional definition of TouchEvent +export function isTouchEvent(event?: EventType): event is TouchEvent | React.TouchEvent { + return !!event && 'changedTouches' in event; +} + +// functional definition of event +export function isEvent(event?: EventType | Element): event is EventType { + return ( + !!event && + (event instanceof Event || ('nativeEvent' in event && event.nativeEvent instanceof Event)) + ); +} diff --git a/packages/vx-event/src/types.ts b/packages/vx-event/src/types.ts new file mode 100644 index 000000000..8ec87341d --- /dev/null +++ b/packages/vx-event/src/types.ts @@ -0,0 +1 @@ +export type EventType = MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent; diff --git a/packages/vx-event/test/localPoint.test.js b/packages/vx-event/test/localPoint.test.ts similarity index 100% rename from packages/vx-event/test/localPoint.test.js rename to packages/vx-event/test/localPoint.test.ts