diff --git a/compat/test/browser/events.test.jsx b/compat/test/browser/events.test.jsx index 92fa1166b6..eab93fa842 100644 --- a/compat/test/browser/events.test.jsx +++ b/compat/test/browser/events.test.jsx @@ -138,13 +138,19 @@ describe('preact/compat events', () => { expect(proto.addEventListener.mock.calls.length).to.eql(4); expect(proto.addEventListener.mock.calls[0].length).to.eql(3); expect(proto.addEventListener.mock.calls[0][0]).to.eql('touchstart'); - expect(proto.addEventListener.mock.calls[0][2]).to.eql(false); + expect(proto.addEventListener.mock.calls[0][2]).to.eql({ + passive: true, + capture: false + }); expect(proto.addEventListener.mock.calls[1].length).to.eql(3); expect(proto.addEventListener.mock.calls[1][0]).to.eql('touchend'); expect(proto.addEventListener.mock.calls[1][2]).to.eql(false); expect(proto.addEventListener.mock.calls[2].length).to.eql(3); expect(proto.addEventListener.mock.calls[2][0]).to.eql('touchmove'); - expect(proto.addEventListener.mock.calls[2][2]).to.eql(false); + expect(proto.addEventListener.mock.calls[2][2]).to.eql({ + passive: true, + capture: false + }); expect(proto.addEventListener.mock.calls[3].length).to.eql(3); expect(proto.addEventListener.mock.calls[3][0]).to.eql('touchcancel'); expect(proto.addEventListener.mock.calls[3][2]).to.eql(false); @@ -286,7 +292,7 @@ describe('preact/compat events', () => { expect(proto.addEventListener).toHaveBeenCalledWith( 'touchmove', expect.any(Function), - true + { passive: true, capture: true } ); }); } diff --git a/src/diff/props.js b/src/diff/props.js index bd12d19418..ce11932ff7 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -85,7 +85,13 @@ export function setProperty(dom, name, value, oldValue, namespace) { dom.addEventListener( name, useCapture ? eventProxyCapture : eventProxy, - useCapture + // touchstart/touchmove/wheel are attached passively to keep + // scrolling off the main thread; matches React 18. Calling + // `preventDefault()` in these handlers is a no-op — attach a + // non-passive listener via a ref for that. + name == 'touchstart' || name == 'touchmove' || name == 'wheel' + ? { passive: true, capture: useCapture } + : useCapture ); } else { value[EVENT_ATTACHED] = oldValue[EVENT_ATTACHED]; diff --git a/test/browser/events.test.jsx b/test/browser/events.test.jsx index 456b645c51..790758e4bc 100644 --- a/test/browser/events.test.jsx +++ b/test/browser/events.test.jsx @@ -182,6 +182,80 @@ describe('event handling', () => { ); }); + it('should attach touch and wheel listeners as passive', () => { + render( +