Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions dev/examples/mouse-events.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react"
import { useRef } from "react"
import { usePanGesture } from "@framer"
import { Box } from "../styled"
export const App = () => {
const ref = useRef(null)
const [point, setPoint] = React.useState({ x: 0, y: 0 })
usePanGesture(
{
onPan: ({ devicePoint }) => {
setPoint(devicePoint)
},
},
ref
)
return <Box ref={ref} style={{ left: point.x, top: point.y, position: "absolute" }} />
}
59 changes: 59 additions & 0 deletions src/events/event-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { EventInfo, Point, EventHandler } from "./types"

interface EventLike {
pageX: number
pageY: number
target: EventTarget | null
}

const pointForTarget = (event: EventLike, target: HTMLElement | null): Point => {
if (!target) {
return { x: event.pageX, y: event.pageY }
}
// Safari
if (window.webkitConvertPointFromPageToNode) {
let webkitPoint = new WebKitPoint(event.pageX, event.pageY)
webkitPoint = window.webkitConvertPointFromPageToNode(target, webkitPoint)
return { x: webkitPoint.x, y: webkitPoint.y }
}
// All other browsers
// TODO: This does not work with rotate yet
const rect = target.getBoundingClientRect()
let scaleX = 1
if (target.style.width && target.style.width !== "") {
scaleX = parseFloat(target.style.width) / rect.width
}
let scaleY = 1
if (target.style.height && target.style.height !== "") {
scaleY = parseFloat(target.style.height) / rect.height
}
const scale = {
x: scaleX,
y: scaleY,
}
const point = {
x: scale.x * (event.pageX - rect.left - target.clientLeft + target.scrollLeft),
y: scale.y * (event.pageY - rect.top - target.clientTop + target.scrollTop),
}
return point
}

const extractEventInfo = (event: EventLike): EventInfo => {
const target = event.target instanceof HTMLElement ? event.target : null
const point = pointForTarget(event, target)
const devicePoint = pointForTarget(event, document.body)
return { point, devicePoint }
}

export const wrapHandler = (handler?: EventHandler): EventListener | undefined => {
if (!handler) {
return undefined
}
const listener: EventListener = (event: any, info?: EventInfo) => {
if (!info) {
info = extractEventInfo(event)
}
handler(event, info)
}
return listener
}
2 changes: 2 additions & 0 deletions src/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useMouseEvents, useTouchEvents, usePointerEvents } from "./use-pointer-events"
export { Point, EventInfo } from "./types"
11 changes: 11 additions & 0 deletions src/events/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface Point {
x: number
y: number
}

export interface EventInfo {
point: Point
devicePoint: Point
}

export type EventHandler = (event: Event, info: EventInfo) => void
39 changes: 39 additions & 0 deletions src/events/use-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RefObject, useEffect } from "react"

export const eventListener = (
target: EventTarget,
name: string,
handler: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions
): [() => void, () => void] => {
const startListening = () => {
target.addEventListener(name, handler, options)
}
const stopListening = () => {
target.removeEventListener(name, handler, options)
}
return [startListening, stopListening]
}

export const useEvent = (
type: string,
ref: RefObject<EventTarget> | EventTarget,
handler?: EventListener,
options?: AddEventListenerOptions
): [() => void, () => void] | undefined => {
if (!handler) {
return
}
if (ref instanceof EventTarget) {
return eventListener(ref, type, handler, options)
}
useEffect(() => {
if (!handler || !ref.current) {
return
}
const [start, stop] = eventListener(ref.current, type, handler, options)
start()
return stop
})
return
}
86 changes: 86 additions & 0 deletions src/events/use-pointer-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { RefObject } from "react"
import { useEvent } from "./use-event"
import { wrapHandler } from "./event-info"
import { EventHandler } from "./types"

const mergeUseEventResults = (
...values: ([() => void, () => void] | undefined)[]
): [() => void, () => void] | undefined => {
if (values.every(v => v === undefined)) {
return
}
const start = () => {
for (const value of values) {
if (value) {
value[0]()
}
}
}
const stop = () => {
for (const value of values) {
if (value) {
value[1]()
}
}
}
return [start, stop]
}

export const useMouseEvents = (
{ onMouseDown, onMouseMove, onMouseUp }: { [key: string]: EventHandler },
ref: RefObject<EventTarget> | EventTarget,
options?: AddEventListenerOptions
): [() => void, () => void] | undefined => {
const down = useEvent("mousedown", ref, wrapHandler(onMouseDown), options)
const move = useEvent("mousemove", ref, wrapHandler(onMouseMove), options)
const up = useEvent("mouseup", ref, wrapHandler(onMouseUp), options)
return mergeUseEventResults(down, move, up)
}

export const useTouchEvents = (
{ onTouchStart, onTouchMove, onTouchEnd }: { [key: string]: EventHandler },
ref: RefObject<EventTarget> | EventTarget,
options?: AddEventListenerOptions
) => {
const down = useEvent("touchstart", ref, wrapHandler(onTouchStart), options)
const move = useEvent("touchmove", ref, wrapHandler(onTouchMove), options)
const up = useEvent("touchend", ref, wrapHandler(onTouchEnd), options)
return mergeUseEventResults(down, move, up)
}

export const useNativePointerEvents = (
{ onPointerDown, onPointerMove, onPointerUp }: { [key: string]: EventHandler },
ref: RefObject<EventTarget> | EventTarget,
options?: AddEventListenerOptions
) => {
const down = useEvent("pointerdown", ref, wrapHandler(onPointerDown), options)
const move = useEvent("pointermove", ref, wrapHandler(onPointerMove), options)
const up = useEvent("pointerup", ref, wrapHandler(onPointerUp), options)
return mergeUseEventResults(down, move, up)
}

const supportsPointerEvents =
window.onpointerdown === null && window.onpointermove === null && window.onpointerup === null
const supportsTouchEvents = window.ontouchstart === null && window.ontouchmove === null && window.ontouchend === null
const supportsMouseEvents = window.onmousedown === null && window.onmousemove === null && window.onmouseup === null

export const usePointerEvents = (
{ onPointerDown, onPointerMove, onPointerUp }: { [key: string]: EventHandler },
ref: RefObject<EventTarget> | EventTarget,
options?: AddEventListenerOptions
) => {
let mouseEvents = {},
touchEvents = {},
pointerEvents = {}
if (supportsPointerEvents) {
pointerEvents = { onPointerDown, onPointerMove, onPointerUp }
} else if (supportsTouchEvents) {
touchEvents = { onTouchStart: onPointerDown, onTouchMove: onPointerMove, onTouchEnd: onPointerUp }
} else if (supportsMouseEvents) {
mouseEvents = { onMouseDown: onPointerDown, onMouseMove: onPointerMove, onMouseUp: onPointerUp }
}
const pointer = useNativePointerEvents(pointerEvents, ref, options)
const touch = useTouchEvents(touchEvents, ref, options)
const mouse = useMouseEvents(mouseEvents, ref, options)
return mergeUseEventResults(pointer, touch, mouse) || []
}
1 change: 1 addition & 0 deletions src/gestures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { usePanGesture } from "./use-pan-gesture"
88 changes: 88 additions & 0 deletions src/gestures/use-pan-gesture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { RefObject, useMemo } from "react"
import { EventInfo, usePointerEvents, Point } from "../events"

export const usePanGesture = (
{
onPan,
onPanStart,
onPanEnd,
}: { [key: string]: (info: { point: Point; devicePoint: Point; delta: Point }, event: Event) => void },
ref: RefObject<Element>
) => {
let session: null | any = null
const onPointerMove = useMemo(
() => {
return (event: Event, { point, devicePoint }: EventInfo) => {
if (!session) {
// tslint:disable-next-line:no-console
console.error("Pointer move without started session")
return
}

const delta = {
x: devicePoint.x - session.lastDevicePoint.x,
y: devicePoint.y - session.lastDevicePoint.y,
}
if (Math.abs(delta.x) > 0 || Math.abs(delta.y) > 0) {
if (session.startEvent) {
if (onPan) {
onPan({ point, devicePoint, delta }, event)
}
} else {
if (onPanStart) {
onPanStart({ point, devicePoint, delta }, event)
}
session.startEvent = event
}
}
session.lastDevicePoint = devicePoint
}
},
[onPan, onPanStart]
)
const onPointerUp = useMemo(
() => {
return (event: Event, { point, devicePoint }: EventInfo) => {
if (!session) {
// tslint:disable-next-line:no-console
console.error("Pointer end without started session")
return
}
const delta = {
x: devicePoint.x - session.lastDevicePoint.x,
y: devicePoint.y - session.lastDevicePoint.y,
}
stopPointerMove()
stopPointerUp()
if (onPanEnd) {
onPanEnd({ point, devicePoint, delta }, event)
}
session = null
}
},
[onPanEnd, onPointerMove]
)

const [startPointerUp, stopPointerUp] = usePointerEvents({ onPointerUp }, window)
const [startPointerMove, stopPointerMove] = usePointerEvents({ onPointerMove }, window, { capture: true })
const onPointerDown = useMemo(
() => {
return (event: Event, { devicePoint }: EventInfo) => {
session = {
target: event.target,
lastDevicePoint: devicePoint,
}
startPointerMove()
startPointerUp()
}
},
[onPointerUp, onPointerMove]
)

usePointerEvents({ onPointerDown }, ref)

// TODO
const handlers = {}

return handlers
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ import { useTransform } from "./hooks/use-transform"
import { usePose } from "./hooks/use-pose"

export { motion, useMotionValue, useTransform, usePose }

export { useMouseEvents, useTouchEvents, usePointerEvents } from "./events"
export { usePanGesture } from "./gestures"