Skip to content

Commit

Permalink
Fixes to event dispatching (#403)
Browse files Browse the repository at this point in the history
* Make immediatePropogationStopped private-ish

* Use workspace polyfill

* Stopping immediate propagation also stops regular propagation

* Assorted dispatching fixes

- Respect stopPropagation throughout both capturing and bubbling
- Call listeners on the target element in both the capturing and bubbling phases
- Simplify returning defaultPrevented

* Add changeset

* Better param name

* Fix lockfile

* Trying again…
  • Loading branch information
jakearchibald authored Aug 9, 2024
1 parent e0c7bd0 commit 0ce1450
Show file tree
Hide file tree
Showing 6 changed files with 1,597 additions and 1,795 deletions.
9 changes: 9 additions & 0 deletions .changeset/strong-sheep-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@remote-dom/polyfill': patch
---

Bug fixes to event dispatching

- Listeners on the target are now called during both the capture and bubble phases.
- `stopPropagation` now respected.
- `stopImmediatePropagation` now also stops regular propagation.
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"build": "rollup --config ./rollup.config.js"
},
"dependencies": {
"@remote-dom/polyfill": "^1.0.3",
"@remote-dom/polyfill": "workspace:^1.0.3",
"htm": "^3.1.1"
},
"peerDependencies": {
Expand Down
40 changes: 19 additions & 21 deletions packages/polyfill/source/Event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {PATH, IS_TRUSTED, LISTENERS} from './constants.ts';
import {
PATH,
IS_TRUSTED,
LISTENERS,
STOP_IMMEDIATE_PROPAGATION,
} from './constants.ts';
import type {EventTarget} from './EventTarget.ts';

export const enum EventPhase {
Expand Down Expand Up @@ -41,12 +46,12 @@ export class Event {
composed = false;
defaultPrevented = false;
cancelBubble = false;
immediatePropagationStopped = false;
eventPhase: EventPhase = 0;
// private inPassiveListener = false;
data?: any;
[PATH]: EventTarget[] = [];
[IS_TRUSTED]!: boolean;
[STOP_IMMEDIATE_PROPAGATION] = false;

constructor(
public type: string,
Expand All @@ -73,7 +78,8 @@ export class Event {
}

stopImmediatePropagation() {
this.immediatePropagationStopped = true;
this[STOP_IMMEDIATE_PROPAGATION] = true;
this.cancelBubble = true;
}

preventDefault() {
Expand All @@ -98,43 +104,35 @@ export class Event {

export function fireEvent(
event: Event,
target: EventTarget,
phase: EventPhase,
) {
const listeners = target[LISTENERS];
currentTarget: EventTarget,
phase: EventPhase.BUBBLING_PHASE | EventPhase.CAPTURING_PHASE,
): void {
const listeners = currentTarget[LISTENERS];
const list = listeners?.get(
`${event.type}${
phase === EventPhase.CAPTURING_PHASE ? CAPTURE_MARKER : ''
}`,
);
if (!list) return false;

let defaultPrevented = false;
if (!list) return;

for (const listener of list) {
event.eventPhase = phase;
event.currentTarget = target;
event.eventPhase =
event.target === currentTarget ? EventPhase.AT_TARGET : phase;
event.currentTarget = currentTarget;

try {
if (typeof listener === 'object') {
listener.handleEvent(event as any);
} else {
listener.call(target, event as any);
listener.call(currentTarget, event as any);
}
} catch (err) {
setTimeout(thrower, 0, err);
}

if (event.defaultPrevented === true) {
defaultPrevented = true;
}

if (event.immediatePropagationStopped) {
break;
}
if (event[STOP_IMMEDIATE_PROPAGATION]) break;
}

return defaultPrevented;
}

function thrower(error: any) {
Expand Down
27 changes: 12 additions & 15 deletions packages/polyfill/source/EventTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,20 @@ export class EventTarget {
event.target = this;
event.srcElement = this;
event[PATH] = path;
let defaultPrevented = false;
for (let i = path.length; --i; ) {
if (fireEvent(event, path[i]!, EventPhase.CAPTURING_PHASE)) {
defaultPrevented = true;
}
}
if (fireEvent(event, this, EventPhase.AT_TARGET)) {
defaultPrevented = true;

for (let i = path.length; i--; ) {
fireEvent(event, path[i]!, EventPhase.CAPTURING_PHASE);
if (event.cancelBubble) return event.defaultPrevented;
}
if (event.bubbles) {
for (let i = 1; i < path.length; i++) {
if (fireEvent(event, path[i]!, EventPhase.BUBBLING_PHASE)) {
defaultPrevented = true;
}
}

const bubblePath = event.bubbles ? path : path.slice(0, 1);

for (let i = 0; i < bubblePath.length; i++) {
fireEvent(event, bubblePath[i]!, EventPhase.BUBBLING_PHASE);
if (event.cancelBubble) return event.defaultPrevented;
}
return !defaultPrevented;

return event.defaultPrevented;
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/polyfill/source/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const USER_PROPERTIES = Symbol('user_properties');
export const LISTENERS = Symbol('listeners');
export const IS_TRUSTED = Symbol('isTrusted');
export const PATH = Symbol('path');
export const STOP_IMMEDIATE_PROPAGATION = Symbol('stop_immediate_propagation');
export const CONTENT = Symbol('content');
export const SLOT = Symbol('slot');

Expand Down
Loading

0 comments on commit 0ce1450

Please sign in to comment.