diff --git a/compat/test/browser/events.test.js b/compat/test/browser/events.test.js
index b7a9d1d7fe..d59dbef8ed 100644
--- a/compat/test/browser/events.test.js
+++ b/compat/test/browser/events.test.js
@@ -2,7 +2,8 @@ import { render } from 'preact';
import {
setupScratch,
teardown,
- createEvent
+ createEvent,
+ supportsPassiveEvents
} from '../../../test/_util/helpers';
import React, { createElement } from 'preact/compat';
@@ -309,4 +310,24 @@ describe('preact/compat events', () => {
scratch.firstChild.dispatchEvent(createEvent('focusout'));
expect(spy).to.be.calledOnce;
});
+
+ if (supportsPassiveEvents()) {
+ it('should use capturing for event props ending with *Capture', () => {
+ let click = sinon.spy();
+
+ render(
+
+
+
,
+ scratch
+ );
+
+ expect(proto.addEventListener).to.have.been.calledOnce;
+ expect(proto.addEventListener).to.have.been.calledWithExactly(
+ 'touchmove',
+ sinon.match.func,
+ true
+ );
+ });
+ }
});
diff --git a/hooks/src/index.js b/hooks/src/index.js
index eadbd86c20..92eb1e113b 100644
--- a/hooks/src/index.js
+++ b/hooks/src/index.js
@@ -25,6 +25,7 @@ let oldBeforeRender = options._render;
let oldAfterDiff = options.diffed;
let oldCommit = options._commit;
let oldBeforeUnmount = options.unmount;
+let oldRoot = options._root;
const RAF_TIMEOUT = 100;
let prevRaf;
@@ -35,6 +36,14 @@ options._diff = vnode => {
if (oldBeforeDiff) oldBeforeDiff(vnode);
};
+options._root = (vnode, parentDom) => {
+ if (parentDom._children && parentDom._children._mask) {
+ vnode._mask = parentDom._children._mask;
+ }
+
+ if (oldRoot) oldRoot(vnode, parentDom);
+};
+
/** @type {(vnode: import('./internal').VNode) => void} */
options._render = vnode => {
if (oldBeforeRender) oldBeforeRender(vnode);
diff --git a/hooks/test/browser/useId.test.js b/hooks/test/browser/useId.test.js
index e7fc7f947b..fe2546fd1a 100644
--- a/hooks/test/browser/useId.test.js
+++ b/hooks/test/browser/useId.test.js
@@ -433,4 +433,27 @@ describe('useId', () => {
rerender();
expect(first).not.to.equal(scratch.innerHTML);
});
+
+ it('should return a unique id across invocations of render', () => {
+ const Id = () => {
+ const id = useId();
+ return My id is {id}
;
+ };
+
+ const App = props => {
+ return (
+
+
+ {props.secondId ? : null}
+
+ );
+ };
+
+ render(createElement(App, { secondId: false }), scratch);
+ expect(scratch.innerHTML).to.equal('');
+ render(createElement(App, { secondId: true }), scratch);
+ expect(scratch.innerHTML).to.equal(
+ 'My id is P0-0
My id is P0-1
'
+ );
+ });
});
diff --git a/src/diff/children.js b/src/diff/children.js
index 369014af39..8256e45d74 100644
--- a/src/diff/children.js
+++ b/src/diff/children.js
@@ -367,7 +367,11 @@ function insert(parentVNode, oldDom, parentDom) {
oldDom = parentVNode._dom;
}
- return oldDom && oldDom.nextSibling;
+ do {
+ oldDom = oldDom && oldDom.nextSibling;
+ } while (oldDom != null && oldDom.nodeType === 8);
+
+ return oldDom;
}
/**
diff --git a/src/diff/props.js b/src/diff/props.js
index 6421330bd0..3ec7e91141 100644
--- a/src/diff/props.js
+++ b/src/diff/props.js
@@ -52,7 +52,7 @@ export function setProperty(dom, name, value, oldValue, isSvg) {
// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
else if (name[0] === 'o' && name[1] === 'n') {
useCapture =
- name !== (name = name.replace(/(PointerCapture)$|Capture$/, '$1'));
+ name !== (name = name.replace(/(PointerCapture)$|Capture$/i, '$1'));
// Infer correct casing for DOM built-in events:
if (name.toLowerCase() in dom) name = name.toLowerCase().slice(2);
diff --git a/test/browser/hydrate.test.js b/test/browser/hydrate.test.js
index 0b23c6f12c..b03c0e2a66 100644
--- a/test/browser/hydrate.test.js
+++ b/test/browser/hydrate.test.js
@@ -50,11 +50,11 @@ describe('hydrate()', () => {
beforeEach(() => {
scratch = setupScratch();
attributesSpy = spyOnElementAttributes();
+ clearLog();
});
afterEach(() => {
teardown(scratch);
- clearLog();
});
it('should reuse existing DOM', () => {
@@ -92,6 +92,7 @@ describe('hydrate()', () => {
scratch
);
expect(scratch.innerHTML).to.equal('01
');
+ expect(getLog()).to.deep.equal(['Comment.remove()']);
});
it('should reuse existing DOM when given components', () => {
@@ -458,5 +459,13 @@ describe('hydrate()', () => {
scratch.innerHTML = 'hello foo
';
hydrate(hello {'foo'}
, scratch);
expect(scratch.innerHTML).to.equal('hello foo
');
+ expect(getLog()).to.deep.equal(['Comment.remove()']);
+ });
+
+ it('should skip over multiple comment nodes', () => {
+ scratch.innerHTML = 'hello foo
';
+ hydrate(hello {'foo'}
, scratch);
+ expect(scratch.innerHTML).to.equal('hello foo
');
+ expect(getLog()).to.deep.equal(['Comment.remove()', 'Comment.remove()']);
});
});