@@ -13,6 +13,23 @@ function setStyle(style, key, value) {
13
13
}
14
14
}
15
15
16
+ // A "virtual clock" to solve issues like https://github.com/preactjs/preact/issues/3927.
17
+ // When the DOM performs an event it leaves micro-ticks in between bubbling up which means that
18
+ // an event can trigger on a newly reated DOM-node while the event bubbles up.
19
+ //
20
+ // Originally inspired by Vue https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/modules/events.ts#L90-L101,
21
+ // but modified to use a virtual clock instead of Date.now() in case event handlers get attached and
22
+ // events get dispatched during the same millisecond.
23
+ //
24
+ // Odd values are reserved for event dispatch times, and even values are reserved for new
25
+ // event handler attachment times.
26
+ //
27
+ // The clock is incremented before a new event is dispatched if the value is even
28
+ // (i.e a new event handler was attached after the previous new event).
29
+ // The clock is also incremented when a new event handler gets attached if the value is odd
30
+ // (i.e. a new event was dispatched after the previous new event dispatch).
31
+ let eventClock = 0 ;
32
+
16
33
/**
17
34
* Set a property value on a DOM node
18
35
* @param {PreactElement } dom The DOM node to modify
@@ -68,7 +85,16 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
68
85
69
86
if ( value ) {
70
87
if ( ! oldValue ) {
71
- value . _attached = Date . now ( ) ;
88
+ // If any new events were dispatched between this moment and the last time
89
+ // an event handler was attached (i.e. `eventClock` is an odd number),
90
+ // then increment `eventClock` first.
91
+ //
92
+ // The following line is a compacted version of:
93
+ // if (eventClock % 2 === 1) {
94
+ // eventClock += 1;
95
+ // }
96
+ // value._attached = eventClock;
97
+ value . _attached = eventClock += eventClock % 2 ;
72
98
const handler = useCapture ? eventProxyCapture : eventProxy ;
73
99
dom . addEventListener ( name , handler , useCapture ) ;
74
100
} else {
@@ -131,18 +157,20 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
131
157
function eventProxy ( e ) {
132
158
if ( this . _listeners ) {
133
159
const eventHandler = this . _listeners [ e . type + false ] ;
134
- /**
135
- * This trick is inspired by Vue https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/modules/events.ts#L90-L101
136
- * when the dom performs an event it leaves micro-ticks in between bubbling up which means that an event can trigger on a newly
137
- * created DOM-node while the event bubbles up, this can cause quirky behavior as seen in https://github.com/preactjs/preact/issues/3927
138
- */
160
+ // If e._dispatched is set, it has to be an odd number, so !e._dispatched must be true if set.
139
161
if ( ! e . _dispatched ) {
140
- // When an event has no _dispatched we know this is the first event-target in the chain
141
- // so we set the initial dispatched time.
142
- e . _dispatched = Date . now ( ) ;
162
+ // If any new event handlers were attached after the previous new event dispatch
163
+ // (i.e. `eventClock` is an even number), then increment `eventClock` first.
164
+ //
165
+ // The following line is a compacted version of:
166
+ // if (eventClock % 2 === 0) {
167
+ // eventClock += 1;
168
+ // }
169
+ // e._dispatched = eventClock;
170
+ e . _dispatched = eventClock += ( eventClock + 1 ) % 2 ;
143
171
// When the _dispatched is smaller than the time when the targetted event handler was attached
144
172
// we know we have bubbled up to an element that was added during patching the dom.
145
- } else if ( e . _dispatched <= eventHandler . _attached ) {
173
+ } else if ( e . _dispatched < eventHandler . _attached ) {
146
174
return ;
147
175
}
148
176
return eventHandler ( options . event ? options . event ( e ) : e ) ;
0 commit comments