diff --git a/dev/examples/Events-whileTap-global.tsx b/dev/examples/Events-whileTap-global.tsx
new file mode 100644
index 0000000000..48f6e33ee5
--- /dev/null
+++ b/dev/examples/Events-whileTap-global.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+import { motion } from "framer-motion"
+
+const style = {
+ width: 100,
+ height: 100,
+ background: "rgba(255, 0, 0, 1)",
+}
+
+export const App = () => {
+ return (
+
+
+
+ )
+}
diff --git a/lerna.json b/lerna.json
index 9e753b6b08..a9d71581fb 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,8 +1,6 @@
{
"version": "10.17.12",
- "packages": [
- "packages/*"
- ],
+ "packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true
}
diff --git a/packages/framer-motion-3d/package.json b/packages/framer-motion-3d/package.json
index aae6d6f2a7..43157ab3fa 100644
--- a/packages/framer-motion-3d/package.json
+++ b/packages/framer-motion-3d/package.json
@@ -59,6 +59,5 @@
"@react-three/fiber": "^8.2.2",
"@react-three/test-renderer": "^9.0.0",
"@rollup/plugin-commonjs": "^22.0.1"
- },
- "gitHead": "f8972cf2de9a59e65915468cf4ff7126789a8d2f"
+ }
}
diff --git a/packages/framer-motion/package.json b/packages/framer-motion/package.json
index eb2690947c..e173e28b5d 100644
--- a/packages/framer-motion/package.json
+++ b/packages/framer-motion/package.json
@@ -113,8 +113,7 @@
},
{
"path": "./dist/size-webpack-dom-max.js",
- "maxSize": "31.88 kB"
+ "maxSize": "31.89 kB"
}
- ],
- "gitHead": "f8972cf2de9a59e65915468cf4ff7126789a8d2f"
+ ]
}
diff --git a/packages/framer-motion/src/gestures/__tests__/press.test.tsx b/packages/framer-motion/src/gestures/__tests__/press.test.tsx
index 45a8fc3b77..77ebe730b2 100644
--- a/packages/framer-motion/src/gestures/__tests__/press.test.tsx
+++ b/packages/framer-motion/src/gestures/__tests__/press.test.tsx
@@ -32,6 +32,26 @@ describe("press", () => {
expect(press).toBeCalledTimes(1)
})
+ test("global press event listeners fire", async () => {
+ const press = jest.fn()
+ const Component = () => (
+ <>
+
+ press()} />
+ >
+ )
+
+ const { getByTestId, rerender } = render()
+ rerender()
+
+ pointerDown(getByTestId("target") as Element)
+ pointerUp(getByTestId("target") as Element)
+
+ await nextFrame()
+
+ expect(press).toBeCalledTimes(1)
+ })
+
test("press event listeners fire via keyboard", async () => {
const press = jest.fn()
const pressStart = jest.fn()
diff --git a/packages/framer-motion/src/gestures/press.ts b/packages/framer-motion/src/gestures/press.ts
index ce26490f69..5561fb0743 100644
--- a/packages/framer-motion/src/gestures/press.ts
+++ b/packages/framer-motion/src/gestures/press.ts
@@ -62,10 +62,10 @@ export class PressGesture extends Feature {
startEvent: PointerEvent,
startInfo: EventInfo
) => {
- this.removeEndListeners()
-
if (this.isPressing) return
+ this.removeEndListeners()
+
const props = this.node.getProps()
const endPointerPress = (
@@ -74,13 +74,14 @@ export class PressGesture extends Feature {
) => {
if (!this.checkPressEnd()) return
- const { onTap, onTapCancel } = this.node.getProps()
+ const { onTap, onTapCancel, globalTapTarget } = this.node.getProps()
frame.update(() => {
/**
* We only count this as a tap gesture if the event.target is the same
* as, or a child of, this component's element
*/
+ !globalTapTarget &&
!isNodeOrChild(this.node.current, endEvent.target as Element)
? onTapCancel && onTapCancel(endEvent, endInfo)
: onTap && onTap(endEvent, endInfo)
@@ -176,7 +177,7 @@ export class PressGesture extends Feature {
const props = this.node.getProps()
const removePointerListener = addPointerEvent(
- this.node.current!,
+ props.globalTapTarget ? window : this.node.current!,
"pointerdown",
this.startPointerPress,
{ passive: !(props.onTapStart || props["onPointerStart"]) }
diff --git a/packages/framer-motion/src/gestures/types.ts b/packages/framer-motion/src/gestures/types.ts
index e32c094f56..468fab223b 100644
--- a/packages/framer-motion/src/gestures/types.ts
+++ b/packages/framer-motion/src/gestures/types.ts
@@ -118,6 +118,13 @@ export interface TapHandlers {
* ```
*/
whileTap?: VariantLabels | TargetAndTransition
+
+ /**
+ * If `true`, the tap gesture will attach its start listener to window.
+ *
+ * Note: This is not supported publically.
+ */
+ globalTapTarget?: boolean
}
export type PanHandler = (event: Event, info: PanInfo) => void
diff --git a/packages/framer-motion/src/motion/utils/valid-prop.ts b/packages/framer-motion/src/motion/utils/valid-prop.ts
index fc45ad7fff..f9974855c0 100644
--- a/packages/framer-motion/src/motion/utils/valid-prop.ts
+++ b/packages/framer-motion/src/motion/utils/valid-prop.ts
@@ -19,9 +19,6 @@ const validMotionProps = new Set([
"transformValues",
"custom",
"inherit",
- "onLayoutAnimationStart",
- "onLayoutAnimationComplete",
- "onLayoutMeasure",
"onBeforeLayoutMeasure",
"onAnimationStart",
"onAnimationComplete",
@@ -38,6 +35,7 @@ const validMotionProps = new Set([
"onHoverEnd",
"onViewportEnter",
"onViewportLeave",
+ "globalTapTarget",
"ignoreStrict",
"viewport",
])
@@ -57,6 +55,7 @@ export function isValidMotionProp(key: string) {
key.startsWith("layout") ||
key.startsWith("onTap") ||
key.startsWith("onPan") ||
+ key.startsWith("onLayout") ||
validMotionProps.has(key as keyof MotionProps)
)
}