diff --git a/src/diff/props.js b/src/diff/props.js index 142e1444c9..3a16fef7bb 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -13,6 +13,18 @@ function setStyle(style, key, value) { } } +// BigInt is used for the counter to avoid overflow. +// It is supported by all major browsers except IE and Opera mini +// where the plain number is used as fallback. +// eslint-disable-next-line no-undef +const zero = typeof window != 'undefined' && window.BigInt ? BigInt(0) : 0; +let globalCounter = zero; +let cachedCounter = zero; +const p = Promise.resolve(); +const getCounter = () => + cachedCounter || + (p.then(() => (cachedCounter = zero)), (cachedCounter = ++globalCounter)); + /** * Set a property value on a DOM node * @param {PreactElement} dom The DOM node to modify @@ -68,7 +80,15 @@ export function setProperty(dom, name, value, oldValue, isSvg) { if (value) { if (!oldValue) { - value._attached = Date.now(); + // Note that the counter is incremented when an event listener is newly attached + // for the first time during a render cycle. All event listeners that are newly + // attached during that render cycle will share the same counter value. + // The value will be compared to the _dispatched added to the event itself + // and the event will propagate when _attached is less or equal to _dispatched. + // Potentially the event handler might be shared between multiple nodes, but + // as long as the order of _attached and _dispatched values is correct + // the event bubbling will work. + value._attached = getCounter(); const handler = useCapture ? eventProxyCapture : eventProxy; dom.addEventListener(name, handler, useCapture); } else { @@ -138,11 +158,11 @@ function eventProxy(e) { */ if (!e._dispatched) { // When an event has no _dispatched we know this is the first event-target in the chain - // so we set the initial dispatched time. - e._dispatched = Date.now(); - // When the _dispatched is smaller than the time when the targetted event handler was attached + // so we set the initial dispatched counter value. + e._dispatched = globalCounter; + // If the _dispatched is smaller than the counter when the targetted event handler was attached // we know we have bubbled up to an element that was added during patching the dom. - } else if (e._dispatched <= eventHandler._attached) { + } else if (e._dispatched < eventHandler._attached) { return; } return eventHandler(options.event ? options.event(e) : e); diff --git a/src/internal.d.ts b/src/internal.d.ts index fda7ce89fd..2624e446c3 100644 --- a/src/internal.d.ts +++ b/src/internal.d.ts @@ -120,7 +120,7 @@ declare global { } export interface PreactEvent extends Event { - _dispatched?: number; + _dispatched?: number | bigint; } // We use the `current` property to differentiate between the two kinds of Refs so