diff --git a/compat/src/render.js b/compat/src/render.js
index 5e923667fd..a4f7972bb8 100644
--- a/compat/src/render.js
+++ b/compat/src/render.js
@@ -153,6 +153,11 @@ options.vnode = vnode => {
i = 'onfocusin';
} else if (/^onblur$/i.test(i)) {
i = 'onfocusout';
+ }
+ // See: https://github.com/facebook/react/blob/54f0e0f7308b4d0d51e52e149d3f7e5a207991ee/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js#L432-L447
+ else if (/^on(Touch[MS]|Wh)/.test(i)) {
+ i = i.toLowerCase();
+ value = [value, { passive: true, capture: /Capture$/.test(i) }];
} else if (/^on(Ani|Tra|Tou|BeforeInp|Compo)/.test(i)) {
i = i.toLowerCase();
} else if (nonCustomElement && CAMEL_PROPS.test(i)) {
diff --git a/compat/test/browser/events.test.js b/compat/test/browser/events.test.js
index fa725e138d..a4bf83bec5 100644
--- a/compat/test/browser/events.test.js
+++ b/compat/test/browser/events.test.js
@@ -175,13 +175,19 @@ describe('preact/compat events', () => {
expect(proto.addEventListener.args.length).to.eql(4);
expect(proto.addEventListener.args[0].length).to.eql(3);
expect(proto.addEventListener.args[0][0]).to.eql('touchstart');
- expect(proto.addEventListener.args[0][2]).to.eql(false);
+ expect(proto.addEventListener.args[0][2]).to.eql({
+ passive: true,
+ capture: false
+ });
expect(proto.addEventListener.args[1].length).to.eql(3);
expect(proto.addEventListener.args[1][0]).to.eql('touchend');
expect(proto.addEventListener.args[1][2]).to.eql(false);
expect(proto.addEventListener.args[2].length).to.eql(3);
expect(proto.addEventListener.args[2][0]).to.eql('touchmove');
- expect(proto.addEventListener.args[2][2]).to.eql(false);
+ expect(proto.addEventListener.args[2][2]).to.eql({
+ passive: true,
+ capture: false
+ });
expect(proto.addEventListener.args[3].length).to.eql(3);
expect(proto.addEventListener.args[3][0]).to.eql('touchcancel');
expect(proto.addEventListener.args[3][2]).to.eql(false);
@@ -215,6 +221,41 @@ describe('preact/compat events', () => {
expect(proto.removeEventListener.args[3][2]).to.eql(false);
});
+ it('should makie touchstart, touchemove and wheel events passive', () => {
+ const onTouchStart = sinon.spy();
+ const onTouchMove = sinon.spy();
+ const onWheel = sinon.spy();
+
+ render(
+
,
+ scratch
+ );
+
+ scratch.firstChild.dispatchEvent(createEvent('touchstart'));
+ scratch.firstChild.dispatchEvent(createEvent('touchmove'));
+ scratch.firstChild.dispatchEvent(createEvent('wheel'));
+
+ expect(proto.addEventListener.args[0][0]).to.eql('touchstart');
+ expect(proto.addEventListener.args[0][2]).to.eql({
+ passive: true,
+ capture: false
+ });
+ expect(proto.addEventListener.args[1][0]).to.eql('touchmove');
+ expect(proto.addEventListener.args[1][2]).to.eql({
+ passive: true,
+ capture: false
+ });
+ expect(proto.addEventListener.args[2][0]).to.eql('wheel');
+ expect(proto.addEventListener.args[2][2]).to.eql({
+ passive: true,
+ capture: false
+ });
+ });
+
it('should support onTransitionEnd', () => {
const func = sinon.spy(() => {});
render(, scratch);
diff --git a/src/diff/props.js b/src/diff/props.js
index df5710925a..a0c94cd7c6 100644
--- a/src/diff/props.js
+++ b/src/diff/props.js
@@ -54,7 +54,7 @@ function setStyle(style, key, value) {
* @param {boolean} isSvg Whether or not this DOM node is an SVG node or not
*/
export function setProperty(dom, name, value, oldValue, isSvg) {
- let useCapture;
+ let useCapture, eventOptions;
o: if (name === 'style') {
if (typeof value == 'string') {
@@ -90,12 +90,17 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
else name = name.slice(2);
if (!dom._listeners) dom._listeners = {};
+ if (!useCapture && Array.isArray(value)) {
+ eventOptions = value[1];
+ value = value[0];
+ useCapture = eventOptions.capture || useCapture;
+ }
dom._listeners[name + useCapture] = value;
if (value) {
if (!oldValue) {
const handler = useCapture ? eventProxyCapture : eventProxy;
- dom.addEventListener(name, handler, useCapture);
+ dom.addEventListener(name, handler, eventOptions || useCapture);
}
} else {
const handler = useCapture ? eventProxyCapture : eventProxy;
diff --git a/src/jsx.d.ts b/src/jsx.d.ts
index 2fb6813776..425bf31935 100644
--- a/src/jsx.d.ts
+++ b/src/jsx.d.ts
@@ -376,12 +376,15 @@ export namespace JSXInternal {
readonly currentTarget: Target;
};
- export type TargetedAnimationEvent =
- TargetedEvent;
- export type TargetedClipboardEvent =
- TargetedEvent;
- export type TargetedCompositionEvent =
- TargetedEvent;
+ export type TargetedAnimationEvent<
+ Target extends EventTarget
+ > = TargetedEvent;
+ export type TargetedClipboardEvent<
+ Target extends EventTarget
+ > = TargetedEvent;
+ export type TargetedCompositionEvent<
+ Target extends EventTarget
+ > = TargetedEvent;
export type TargetedDragEvent = TargetedEvent<
Target,
DragEvent
@@ -406,8 +409,9 @@ export namespace JSXInternal {
Target,
TouchEvent
>;
- export type TargetedTransitionEvent =
- TargetedEvent;
+ export type TargetedTransitionEvent<
+ Target extends EventTarget
+ > = TargetedEvent;
export type TargetedUIEvent = TargetedEvent<
Target,
UIEvent
@@ -425,14 +429,22 @@ export namespace JSXInternal {
(this: never, event: E): void;
}
+ export interface EventHandlerOptions {
+ capture?: boolean;
+ once?: boolean;
+ passive?: boolean;
+ signal?: AbortSignal;
+ }
+
export type AnimationEventHandler = EventHandler<
TargetedAnimationEvent
>;
export type ClipboardEventHandler = EventHandler<
TargetedClipboardEvent
>;
- export type CompositionEventHandler =
- EventHandler>;
+ export type CompositionEventHandler<
+ Target extends EventTarget
+ > = EventHandler>;
export type DragEventHandler = EventHandler<
TargetedDragEvent
>;
@@ -467,148 +479,276 @@ export namespace JSXInternal {
export interface DOMAttributes
extends PreactDOMAttributes {
// Image Events
- onLoad?: GenericEventHandler;
+ onLoad?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onLoadCapture?: GenericEventHandler;
- onError?: GenericEventHandler;
+ onError?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onErrorCapture?: GenericEventHandler;
// Clipboard Events
- onCopy?: ClipboardEventHandler;
+ onCopy?:
+ | ClipboardEventHandler
+ | [ClipboardEventHandler, EventHandlerOptions];
onCopyCapture?: ClipboardEventHandler;
- onCut?: ClipboardEventHandler;
+ onCut?:
+ | ClipboardEventHandler
+ | [ClipboardEventHandler, EventHandlerOptions];
onCutCapture?: ClipboardEventHandler;
- onPaste?: ClipboardEventHandler;
+ onPaste?:
+ | ClipboardEventHandler
+ | [ClipboardEventHandler, EventHandlerOptions];
onPasteCapture?: ClipboardEventHandler;
// Composition Events
- onCompositionEnd?: CompositionEventHandler;
+ onCompositionEnd?:
+ | CompositionEventHandler
+ | [CompositionEventHandler, EventHandlerOptions];
onCompositionEndCapture?: CompositionEventHandler;
- onCompositionStart?: CompositionEventHandler;
+ onCompositionStart?:
+ | CompositionEventHandler
+ | [CompositionEventHandler, EventHandlerOptions];
onCompositionStartCapture?: CompositionEventHandler;
- onCompositionUpdate?: CompositionEventHandler;
+ onCompositionUpdate?:
+ | CompositionEventHandler
+ | [CompositionEventHandler, EventHandlerOptions];
onCompositionUpdateCapture?: CompositionEventHandler;
// Details Events
- onToggle?: GenericEventHandler;
+ onToggle?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
// Focus Events
- onFocus?: FocusEventHandler;
+ onFocus?:
+ | FocusEventHandler
+ | [FocusEventHandler, EventHandlerOptions];
onFocusCapture?: FocusEventHandler;
- onfocusin?: FocusEventHandler;
+ onfocusin?:
+ | FocusEventHandler
+ | [FocusEventHandler, EventHandlerOptions];
onfocusinCapture?: FocusEventHandler;
- onfocusout?: FocusEventHandler;
+ onfocusout?:
+ | FocusEventHandler
+ | [FocusEventHandler, EventHandlerOptions];
onfocusoutCapture?: FocusEventHandler;
- onBlur?: FocusEventHandler;
+ onBlur?:
+ | FocusEventHandler
+ | [FocusEventHandler, EventHandlerOptions];
onBlurCapture?: FocusEventHandler;
// Form Events
- onChange?: GenericEventHandler;
+ onChange?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onChangeCapture?: GenericEventHandler;
- onInput?: GenericEventHandler;
+ onInput?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onInputCapture?: GenericEventHandler;
- onBeforeInput?: GenericEventHandler;
+ onBeforeInput?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onBeforeInputCapture?: GenericEventHandler;
- onSearch?: GenericEventHandler;
+ onSearch?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onSearchCapture?: GenericEventHandler;
- onSubmit?: GenericEventHandler;
+ onSubmit?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onSubmitCapture?: GenericEventHandler;
- onInvalid?: GenericEventHandler;
+ onInvalid?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onInvalidCapture?: GenericEventHandler;
- onReset?: GenericEventHandler;
+ onReset?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onResetCapture?: GenericEventHandler;
- onFormData?: GenericEventHandler;
+ onFormData?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onFormDataCapture?: GenericEventHandler;
// Keyboard Events
- onKeyDown?: KeyboardEventHandler;
+ onKeyDown?:
+ | KeyboardEventHandler
+ | [KeyboardEventHandler, EventHandlerOptions];
onKeyDownCapture?: KeyboardEventHandler;
- onKeyPress?: KeyboardEventHandler;
+ onKeyPress?:
+ | KeyboardEventHandler
+ | [KeyboardEventHandler, EventHandlerOptions];
onKeyPressCapture?: KeyboardEventHandler;
- onKeyUp?: KeyboardEventHandler;
+ onKeyUp?:
+ | KeyboardEventHandler
+ | [KeyboardEventHandler, EventHandlerOptions];
onKeyUpCapture?: KeyboardEventHandler;
// Media Events
- onAbort?: GenericEventHandler;
+ onAbort?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onAbortCapture?: GenericEventHandler;
- onCanPlay?: GenericEventHandler;
+ onCanPlay?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onCanPlayCapture?: GenericEventHandler;
- onCanPlayThrough?: GenericEventHandler;
+ onCanPlayThrough?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onCanPlayThroughCapture?: GenericEventHandler;
- onDurationChange?: GenericEventHandler;
+ onDurationChange?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onDurationChangeCapture?: GenericEventHandler;
- onEmptied?: GenericEventHandler;
+ onEmptied?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onEmptiedCapture?: GenericEventHandler;
- onEncrypted?: GenericEventHandler;
+ onEncrypted?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onEncryptedCapture?: GenericEventHandler;
- onEnded?: GenericEventHandler;
+ onEnded?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onEndedCapture?: GenericEventHandler;
- onLoadedData?: GenericEventHandler;
+ onLoadedData?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onLoadedDataCapture?: GenericEventHandler;
- onLoadedMetadata?: GenericEventHandler;
+ onLoadedMetadata?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onLoadedMetadataCapture?: GenericEventHandler;
- onLoadStart?: GenericEventHandler;
+ onLoadStart?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onLoadStartCapture?: GenericEventHandler;
- onPause?: GenericEventHandler;
+ onPause?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onPauseCapture?: GenericEventHandler;
- onPlay?: GenericEventHandler;
+ onPlay?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onPlayCapture?: GenericEventHandler;
- onPlaying?: GenericEventHandler;
+ onPlaying?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onPlayingCapture?: GenericEventHandler;
- onProgress?: GenericEventHandler;
+ onProgress?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onProgressCapture?: GenericEventHandler;
- onRateChange?: GenericEventHandler;
+ onRateChange?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onRateChangeCapture?: GenericEventHandler;
- onSeeked?: GenericEventHandler;
+ onSeeked?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onSeekedCapture?: GenericEventHandler;
- onSeeking?: GenericEventHandler;
+ onSeeking?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onSeekingCapture?: GenericEventHandler;
- onStalled?: GenericEventHandler;
+ onStalled?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onStalledCapture?: GenericEventHandler;
- onSuspend?: GenericEventHandler;
+ onSuspend?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onSuspendCapture?: GenericEventHandler;
- onTimeUpdate?: GenericEventHandler;
+ onTimeUpdate?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onTimeUpdateCapture?: GenericEventHandler;
- onVolumeChange?: GenericEventHandler;
+ onVolumeChange?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onVolumeChangeCapture?: GenericEventHandler;
- onWaiting?: GenericEventHandler;
+ onWaiting?:
+ | GenericEventHandler
+ | [GenericEventHandler, EventHandlerOptions];
onWaitingCapture?: GenericEventHandler;
// MouseEvents
- onClick?: MouseEventHandler;
+ onClick?:
+ | MouseEventHandler
+ | [MouseEventHandler, EventHandlerOptions];
onClickCapture?: MouseEventHandler;
- onContextMenu?: MouseEventHandler;
+ onContextMenu?:
+ | MouseEventHandler
+ | [MouseEventHandler, EventHandlerOptions];
onContextMenuCapture?: MouseEventHandler;
- onDblClick?: MouseEventHandler;
+ onDblClick?:
+ | MouseEventHandler
+ | [MouseEventHandler, EventHandlerOptions];
onDblClickCapture?: MouseEventHandler;
- onDrag?: DragEventHandler;
+ onDrag?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragCapture?: DragEventHandler;
- onDragEnd?: DragEventHandler;
+ onDragEnd?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragEndCapture?: DragEventHandler;
- onDragEnter?: DragEventHandler;
+ onDragEnter?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragEnterCapture?: DragEventHandler;
- onDragExit?: DragEventHandler;
+ onDragExit?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragExitCapture?: DragEventHandler;
- onDragLeave?: DragEventHandler;
+ onDragLeave?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragLeaveCapture?: DragEventHandler;
- onDragOver?: DragEventHandler;
+ onDragOver?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragOverCapture?: DragEventHandler;
- onDragStart?: DragEventHandler;
+ onDragStart?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDragStartCapture?: DragEventHandler;
- onDrop?: DragEventHandler;
+ onDrop?:
+ | DragEventHandler
+ | [DragEventHandler, EventHandlerOptions];
onDropCapture?: DragEventHandler;
- onMouseDown?: MouseEventHandler;
+ onMouseDown?:
+ | MouseEventHandler
+ | [MouseEventHandler