@remote-dom/polyfill 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +51 -3
  3. package/build/cjs/Attr.cjs +33 -4
  4. package/build/cjs/CharacterData.cjs +10 -0
  5. package/build/cjs/ChildNode.cjs +79 -13
  6. package/build/cjs/Comment.cjs +1 -1
  7. package/build/cjs/CustomElementRegistry.cjs +27 -2
  8. package/build/cjs/CustomEvent.cjs +2 -2
  9. package/build/cjs/Document.cjs +56 -32
  10. package/build/cjs/DocumentFragment.cjs +6 -2
  11. package/build/cjs/Element.cjs +68 -8
  12. package/build/cjs/Event.cjs +29 -27
  13. package/build/cjs/EventTarget.cjs +128 -58
  14. package/build/cjs/HTMLTemplateElement.cjs +1 -0
  15. package/build/cjs/MutationObserver.cjs +167 -1
  16. package/build/cjs/NamedNodeMap.cjs +82 -45
  17. package/build/cjs/Node.cjs +47 -11
  18. package/build/cjs/NodeList.cjs +9 -1
  19. package/build/cjs/ParentNode.cjs +319 -77
  20. package/build/cjs/SVGElement.cjs +1 -1
  21. package/build/cjs/Text.cjs +1 -1
  22. package/build/cjs/Window.cjs +131 -38
  23. package/build/cjs/attribute-reactions.cjs +20 -0
  24. package/build/cjs/constants.cjs +48 -24
  25. package/build/cjs/custom-element-reactions.cjs +52 -0
  26. package/build/cjs/dom-exception.cjs +15 -0
  27. package/build/cjs/hook-effects.cjs +51 -0
  28. package/build/cjs/names.cjs +61 -0
  29. package/build/cjs/selectors.cjs +213 -122
  30. package/build/cjs/serialization.cjs +168 -65
  31. package/build/cjs/shared.cjs +177 -28
  32. package/build/esm/Attr.mjs +34 -5
  33. package/build/esm/CharacterData.mjs +10 -0
  34. package/build/esm/ChildNode.mjs +76 -15
  35. package/build/esm/Comment.mjs +2 -2
  36. package/build/esm/CustomElementRegistry.mjs +27 -2
  37. package/build/esm/CustomEvent.mjs +2 -2
  38. package/build/esm/Document.mjs +60 -35
  39. package/build/esm/DocumentFragment.mjs +7 -3
  40. package/build/esm/Element.mjs +69 -9
  41. package/build/esm/Event.mjs +27 -27
  42. package/build/esm/EventTarget.mjs +130 -60
  43. package/build/esm/HTMLTemplateElement.mjs +2 -1
  44. package/build/esm/MutationObserver.mjs +166 -2
  45. package/build/esm/NamedNodeMap.mjs +83 -46
  46. package/build/esm/Node.mjs +48 -12
  47. package/build/esm/NodeList.mjs +9 -1
  48. package/build/esm/ParentNode.mjs +322 -81
  49. package/build/esm/SVGElement.mjs +2 -2
  50. package/build/esm/Text.mjs +2 -2
  51. package/build/esm/Window.mjs +132 -39
  52. package/build/esm/attribute-reactions.mjs +16 -0
  53. package/build/esm/constants.mjs +28 -23
  54. package/build/esm/custom-element-reactions.mjs +47 -0
  55. package/build/esm/dom-exception.mjs +11 -0
  56. package/build/esm/hook-effects.mjs +46 -0
  57. package/build/esm/names.mjs +54 -0
  58. package/build/esm/selectors.mjs +202 -124
  59. package/build/esm/serialization.mjs +169 -66
  60. package/build/esm/shared.mjs +173 -30
  61. package/build/esnext/Attr.esnext +45 -5
  62. package/build/esnext/CharacterData.esnext +10 -0
  63. package/build/esnext/ChildNode.esnext +84 -15
  64. package/build/esnext/Comment.esnext +2 -2
  65. package/build/esnext/CustomElementRegistry.esnext +42 -0
  66. package/build/esnext/CustomEvent.esnext +2 -2
  67. package/build/esnext/Document.esnext +69 -32
  68. package/build/esnext/DocumentFragment.esnext +6 -2
  69. package/build/esnext/Element.esnext +78 -9
  70. package/build/esnext/Event.esnext +36 -37
  71. package/build/esnext/EventTarget.esnext +133 -61
  72. package/build/esnext/HTMLTemplateElement.esnext +3 -1
  73. package/build/esnext/MutationObserver.esnext +163 -2
  74. package/build/esnext/NamedNodeMap.esnext +110 -42
  75. package/build/esnext/Node.esnext +50 -12
  76. package/build/esnext/NodeList.esnext +1 -1
  77. package/build/esnext/ParentNode.esnext +352 -79
  78. package/build/esnext/SVGElement.esnext +2 -2
  79. package/build/esnext/Text.esnext +2 -2
  80. package/build/esnext/Window.esnext +120 -16
  81. package/build/esnext/attribute-reactions.esnext +21 -0
  82. package/build/esnext/constants.esnext +34 -21
  83. package/build/esnext/custom-element-reactions.esnext +49 -0
  84. package/build/esnext/dom-exception.esnext +11 -0
  85. package/build/esnext/hook-effects.esnext +46 -0
  86. package/build/esnext/names.esnext +61 -0
  87. package/build/esnext/selectors.esnext +202 -100
  88. package/build/esnext/serialization.esnext +171 -48
  89. package/build/esnext/shared.esnext +181 -34
  90. package/build/tsconfig.tsbuildinfo +1 -1
  91. package/build/typescript/Attr.d.ts +6 -4
  92. package/build/typescript/Attr.d.ts.map +1 -1
  93. package/build/typescript/CharacterData.d.ts.map +1 -1
  94. package/build/typescript/ChildNode.d.ts +5 -0
  95. package/build/typescript/ChildNode.d.ts.map +1 -1
  96. package/build/typescript/ClipboardEvent.d.ts.map +1 -1
  97. package/build/typescript/Comment.d.ts +2 -2
  98. package/build/typescript/Comment.d.ts.map +1 -1
  99. package/build/typescript/CustomElementRegistry.d.ts +1 -0
  100. package/build/typescript/CustomElementRegistry.d.ts.map +1 -1
  101. package/build/typescript/CustomEvent.d.ts.map +1 -1
  102. package/build/typescript/Document.d.ts +10 -5
  103. package/build/typescript/Document.d.ts.map +1 -1
  104. package/build/typescript/DocumentFragment.d.ts +2 -1
  105. package/build/typescript/DocumentFragment.d.ts.map +1 -1
  106. package/build/typescript/Element.d.ts +16 -7
  107. package/build/typescript/Element.d.ts.map +1 -1
  108. package/build/typescript/ErrorEvent.d.ts.map +1 -1
  109. package/build/typescript/Event.d.ts +9 -10
  110. package/build/typescript/Event.d.ts.map +1 -1
  111. package/build/typescript/EventTarget.d.ts +20 -3
  112. package/build/typescript/EventTarget.d.ts.map +1 -1
  113. package/build/typescript/FocusEvent.d.ts.map +1 -1
  114. package/build/typescript/HTMLTemplateElement.d.ts.map +1 -1
  115. package/build/typescript/MutationObserver.d.ts +42 -0
  116. package/build/typescript/MutationObserver.d.ts.map +1 -1
  117. package/build/typescript/NamedNodeMap.d.ts +5 -3
  118. package/build/typescript/NamedNodeMap.d.ts.map +1 -1
  119. package/build/typescript/Node.d.ts +4 -3
  120. package/build/typescript/Node.d.ts.map +1 -1
  121. package/build/typescript/NodeList.d.ts +3 -2
  122. package/build/typescript/NodeList.d.ts.map +1 -1
  123. package/build/typescript/ParentNode.d.ts +29 -10
  124. package/build/typescript/ParentNode.d.ts.map +1 -1
  125. package/build/typescript/PromiseRejectionEvent.d.ts.map +1 -1
  126. package/build/typescript/SVGElement.d.ts +1 -1
  127. package/build/typescript/SVGElement.d.ts.map +1 -1
  128. package/build/typescript/Text.d.ts +1 -1
  129. package/build/typescript/Text.d.ts.map +1 -1
  130. package/build/typescript/ToggleEvent.d.ts.map +1 -1
  131. package/build/typescript/Window.d.ts +5 -1
  132. package/build/typescript/Window.d.ts.map +1 -1
  133. package/build/typescript/attribute-reactions.d.ts +4 -0
  134. package/build/typescript/attribute-reactions.d.ts.map +1 -0
  135. package/build/typescript/constants.d.ts +27 -18
  136. package/build/typescript/constants.d.ts.map +1 -1
  137. package/build/typescript/custom-element-reactions.d.ts +6 -0
  138. package/build/typescript/custom-element-reactions.d.ts.map +1 -0
  139. package/build/typescript/dom-exception.d.ts +2 -0
  140. package/build/typescript/dom-exception.d.ts.map +1 -0
  141. package/build/typescript/extensions.d.ts +4 -0
  142. package/build/typescript/extensions.d.ts.map +1 -0
  143. package/build/typescript/hook-effects.d.ts +6 -0
  144. package/build/typescript/hook-effects.d.ts.map +1 -0
  145. package/build/typescript/hooks.d.ts +3 -3
  146. package/build/typescript/hooks.d.ts.map +1 -1
  147. package/build/typescript/index.d.ts +1 -0
  148. package/build/typescript/index.d.ts.map +1 -1
  149. package/build/typescript/names.d.ts +11 -0
  150. package/build/typescript/names.d.ts.map +1 -0
  151. package/build/typescript/selectors.d.ts +21 -22
  152. package/build/typescript/selectors.d.ts.map +1 -1
  153. package/build/typescript/serialization.d.ts.map +1 -1
  154. package/build/typescript/shared.d.ts +10 -0
  155. package/build/typescript/shared.d.ts.map +1 -1
  156. package/build/typescript/tests/ChildNode.test.d.ts +2 -0
  157. package/build/typescript/tests/ChildNode.test.d.ts.map +1 -0
  158. package/build/typescript/tests/MutationObserver.test.d.ts +2 -0
  159. package/build/typescript/tests/MutationObserver.test.d.ts.map +1 -0
  160. package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts +2 -0
  161. package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts.map +1 -0
  162. package/build/typescript/tests/attr-mutation.test.d.ts +2 -0
  163. package/build/typescript/tests/attr-mutation.test.d.ts.map +1 -0
  164. package/build/typescript/tests/child-node-before-after.test.d.ts +2 -0
  165. package/build/typescript/tests/child-node-before-after.test.d.ts.map +1 -0
  166. package/build/typescript/tests/child-node-replace-with.test.d.ts +2 -0
  167. package/build/typescript/tests/child-node-replace-with.test.d.ts.map +1 -0
  168. package/build/typescript/tests/clone-import-namespaces.test.d.ts +2 -0
  169. package/build/typescript/tests/clone-import-namespaces.test.d.ts.map +1 -0
  170. package/build/typescript/tests/constants.test.d.ts +2 -0
  171. package/build/typescript/tests/constants.test.d.ts.map +1 -0
  172. package/build/typescript/tests/create-element-namespaces.test.d.ts +2 -0
  173. package/build/typescript/tests/create-element-namespaces.test.d.ts.map +1 -0
  174. package/build/typescript/tests/create-text-node.test.d.ts +2 -0
  175. package/build/typescript/tests/create-text-node.test.d.ts.map +1 -0
  176. package/build/typescript/tests/custom-element-reactions.test.d.ts +2 -0
  177. package/build/typescript/tests/custom-element-reactions.test.d.ts.map +1 -0
  178. package/build/typescript/tests/custom-element-registry.test.d.ts +2 -0
  179. package/build/typescript/tests/custom-element-registry.test.d.ts.map +1 -0
  180. package/build/typescript/tests/custom-event.test.d.ts +2 -0
  181. package/build/typescript/tests/custom-event.test.d.ts.map +1 -0
  182. package/build/typescript/tests/document-clone-import-contract.test.d.ts +2 -0
  183. package/build/typescript/tests/document-clone-import-contract.test.d.ts.map +1 -0
  184. package/build/typescript/tests/document.test.d.ts +2 -0
  185. package/build/typescript/tests/document.test.d.ts.map +1 -0
  186. package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts +2 -0
  187. package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts.map +1 -0
  188. package/build/typescript/tests/event-dispatch.test.d.ts +2 -0
  189. package/build/typescript/tests/event-dispatch.test.d.ts.map +1 -0
  190. package/build/typescript/tests/event-target.test.d.ts +2 -0
  191. package/build/typescript/tests/event-target.test.d.ts.map +1 -0
  192. package/build/typescript/tests/event.test.d.ts +2 -0
  193. package/build/typescript/tests/event.test.d.ts.map +1 -0
  194. package/build/typescript/tests/extensions.test.d.ts +2 -0
  195. package/build/typescript/tests/extensions.test.d.ts.map +1 -0
  196. package/build/typescript/tests/get-elements-by-tag-name.test.d.ts +2 -0
  197. package/build/typescript/tests/get-elements-by-tag-name.test.d.ts.map +1 -0
  198. package/build/typescript/tests/getElementsByClassName.test.d.ts +2 -0
  199. package/build/typescript/tests/getElementsByClassName.test.d.ts.map +1 -0
  200. package/build/typescript/tests/hooks.test.d.ts +2 -0
  201. package/build/typescript/tests/hooks.test.d.ts.map +1 -0
  202. package/build/typescript/tests/inner-html-parsing.test.d.ts +2 -0
  203. package/build/typescript/tests/inner-html-parsing.test.d.ts.map +1 -0
  204. package/build/typescript/tests/is-default-namespace.test.d.ts +2 -0
  205. package/build/typescript/tests/is-default-namespace.test.d.ts.map +1 -0
  206. package/build/typescript/tests/name-and-namespace-normalization.test.d.ts +2 -0
  207. package/build/typescript/tests/name-and-namespace-normalization.test.d.ts.map +1 -0
  208. package/build/typescript/tests/named-node-map.test.d.ts +2 -0
  209. package/build/typescript/tests/named-node-map.test.d.ts.map +1 -0
  210. package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts +2 -0
  211. package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts.map +1 -0
  212. package/build/typescript/tests/node.test.d.ts +2 -0
  213. package/build/typescript/tests/node.test.d.ts.map +1 -0
  214. package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts +2 -0
  215. package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts.map +1 -0
  216. package/build/typescript/tests/parent-node-mutation-validation.test.d.ts +2 -0
  217. package/build/typescript/tests/parent-node-mutation-validation.test.d.ts.map +1 -0
  218. package/build/typescript/tests/parent-node.test.d.ts +2 -0
  219. package/build/typescript/tests/parent-node.test.d.ts.map +1 -0
  220. package/build/typescript/tests/serialization-template.test.d.ts +2 -0
  221. package/build/typescript/tests/serialization-template.test.d.ts.map +1 -0
  222. package/build/typescript/tests/slot-attribute.test.d.ts +2 -0
  223. package/build/typescript/tests/slot-attribute.test.d.ts.map +1 -0
  224. package/build/typescript/tests/text-content.test.d.ts +2 -0
  225. package/build/typescript/tests/text-content.test.d.ts.map +1 -0
  226. package/build/typescript/tests/toggle-attribute.test.d.ts +2 -0
  227. package/build/typescript/tests/toggle-attribute.test.d.ts.map +1 -0
  228. package/build/typescript/tests/wide-tree-traversal.test.d.ts +2 -0
  229. package/build/typescript/tests/wide-tree-traversal.test.d.ts.map +1 -0
  230. package/build/typescript/tests/window-set-global.test.d.ts +2 -0
  231. package/build/typescript/tests/window-set-global.test.d.ts.map +1 -0
  232. package/package.json +1 -1
  233. package/source/Attr.ts +60 -8
  234. package/source/CharacterData.ts +13 -0
  235. package/source/ChildNode.ts +107 -14
  236. package/source/Comment.ts +2 -2
  237. package/source/CustomElementRegistry.ts +56 -0
  238. package/source/CustomEvent.ts +2 -2
  239. package/source/Document.ts +104 -38
  240. package/source/DocumentFragment.ts +12 -2
  241. package/source/Element.ts +129 -14
  242. package/source/Event.ts +34 -32
  243. package/source/EventTarget.ts +203 -79
  244. package/source/HTMLTemplateElement.ts +2 -1
  245. package/source/MutationObserver.ts +279 -1
  246. package/source/NamedNodeMap.ts +141 -60
  247. package/source/Node.ts +70 -14
  248. package/source/NodeList.ts +13 -3
  249. package/source/ParentNode.ts +500 -88
  250. package/source/SVGElement.ts +2 -2
  251. package/source/Text.ts +2 -2
  252. package/source/Window.ts +156 -17
  253. package/source/attribute-reactions.ts +32 -0
  254. package/source/constants.ts +40 -17
  255. package/source/custom-element-reactions.ts +64 -0
  256. package/source/dom-exception.ts +14 -0
  257. package/source/extensions.ts +4 -0
  258. package/source/hook-effects.ts +56 -0
  259. package/source/hooks.ts +11 -3
  260. package/source/index.ts +1 -0
  261. package/source/names.ts +94 -0
  262. package/source/selectors.ts +301 -138
  263. package/source/serialization.ts +224 -66
  264. package/source/shared.ts +269 -40
  265. package/source/tests/ChildNode.test.ts +178 -0
  266. package/source/tests/MutationObserver.test.ts +303 -0
  267. package/source/tests/adopt-inserted-subtrees.test.ts +584 -0
  268. package/source/tests/attr-mutation.test.ts +112 -0
  269. package/source/tests/child-node-before-after.test.ts +421 -0
  270. package/source/tests/child-node-replace-with.test.ts +251 -0
  271. package/source/tests/clone-import-namespaces.test.ts +365 -0
  272. package/source/tests/constants.test.ts +46 -0
  273. package/source/tests/create-element-namespaces.test.ts +142 -0
  274. package/source/tests/create-text-node.test.ts +38 -0
  275. package/source/tests/custom-element-reactions.test.ts +600 -0
  276. package/source/tests/custom-element-registry.test.ts +137 -0
  277. package/source/tests/custom-event.test.ts +26 -0
  278. package/source/tests/document-clone-import-contract.test.ts +77 -0
  279. package/source/tests/document.test.ts +122 -0
  280. package/source/tests/dom-mutation-selector-errors.test.ts +484 -0
  281. package/source/tests/event-dispatch.test.ts +270 -0
  282. package/source/tests/event-target.test.ts +208 -0
  283. package/source/tests/event.test.ts +98 -0
  284. package/source/tests/extensions.test.ts +159 -0
  285. package/source/tests/get-elements-by-tag-name.test.ts +79 -0
  286. package/source/tests/getElementsByClassName.test.ts +109 -0
  287. package/source/tests/hooks.test.ts +43 -0
  288. package/source/tests/inner-html-parsing.test.ts +461 -0
  289. package/source/tests/is-default-namespace.test.ts +111 -0
  290. package/source/tests/name-and-namespace-normalization.test.ts +319 -0
  291. package/source/tests/named-node-map.test.ts +122 -0
  292. package/source/tests/namespaced-attribute-reactions.test.ts +141 -0
  293. package/source/tests/node.test.ts +56 -0
  294. package/source/tests/parent-node-mutation-return-values.test.ts +63 -0
  295. package/source/tests/parent-node-mutation-validation.test.ts +362 -0
  296. package/source/tests/parent-node.test.ts +169 -0
  297. package/source/tests/selectors.test.ts +533 -22
  298. package/source/tests/serialization-template.test.ts +232 -0
  299. package/source/tests/slot-attribute.test.ts +133 -0
  300. package/source/tests/text-content.test.ts +440 -0
  301. package/source/tests/toggle-attribute.test.ts +113 -0
  302. package/source/tests/wide-tree-traversal.test.ts +699 -0
  303. package/source/tests/window-set-global.test.ts +321 -0
  304. package/tsconfig.json +1 -1
@@ -1,5 +1,15 @@
1
- export class NodeList extends Array {
2
- item(index: number) {
3
- return this[index];
1
+ import type {Node} from './Node.ts';
2
+
3
+ // Performance note (2026-09-02): Benchmarks on an Apple M4 Pro with Node.js
4
+ // 24.19.0 (V8 13.6.233.17-node.51) and Chromium 152 showed indexed access and
5
+ // `for...of` iteration at parity with Array. Construction, `push()`, and some
6
+ // inherited methods (`forEach()`, `includes()`, `map()`, `filter()`, and
7
+ // `slice()`) were slower, but the measured end-to-end `querySelectorAll()`
8
+ // overhead was 0–12% (up to about 1 µs in the tested 100–1,000-element trees)
9
+ // and decreased as selector traversal dominated. Keep this direct subclass
10
+ // while indexed access and iteration remain the primary usage patterns.
11
+ export class NodeList<Item extends Node = Node> extends Array<Item> {
12
+ item(index: number): Item | null {
13
+ return this[index] ?? null;
4
14
  }
5
15
  }
@@ -4,93 +4,203 @@ import {
4
4
  PREV,
5
5
  PARENT,
6
6
  OWNER_DOCUMENT,
7
- NodeType,
7
+ NODE_TYPE_DOCUMENT_FRAGMENT,
8
+ NODE_TYPE_ELEMENT,
8
9
  HOOKS,
9
10
  IS_CONNECTED,
10
11
  } from './constants.ts';
11
12
  import type {Node} from './Node.ts';
12
- import {ChildNode, toNode} from './ChildNode.ts';
13
+ import type {Element} from './Element.ts';
14
+ import {
15
+ ChildNode,
16
+ INSERT_NODE,
17
+ PREFLIGHT_INSERTIONS,
18
+ REPLACE_NODE,
19
+ stageNodes,
20
+ toNode,
21
+ validateInsertionNodes,
22
+ } from './ChildNode.ts';
13
23
  import {NodeList} from './NodeList.ts';
14
24
  import {querySelectorAll, querySelector} from './selectors.ts';
15
- import {selfAndDescendants} from './shared.ts';
25
+ import {
26
+ adoptNodes,
27
+ collectAdoptionSnapshot,
28
+ isElementNode,
29
+ selfAndDescendants,
30
+ } from './shared.ts';
31
+ import {
32
+ childListObserversActive,
33
+ mutationNodeList,
34
+ queueMutationRecord,
35
+ } from './MutationObserver.ts';
36
+ import {
37
+ enqueueCustomElementReaction,
38
+ performWithCustomElementReactions,
39
+ } from './custom-element-reactions.ts';
40
+ import {
41
+ isCoveredByPendingPublication,
42
+ performHookEffects,
43
+ type HookEffect,
44
+ } from './hook-effects.ts';
45
+ import {createDOMException} from './dom-exception.ts';
46
+
47
+ interface PreparedInsertionRoot {
48
+ node: Node;
49
+ nodes: Node[] | undefined;
50
+ adoption: Node[] | undefined;
51
+ shouldDisconnect: boolean;
52
+ source?: {
53
+ parent: ParentNode;
54
+ index: number;
55
+ previousSibling: Node | null;
56
+ nextSibling: Node | null;
57
+ };
58
+ insertIndex?: number;
59
+ previousSibling?: Node | null;
60
+ nextSibling?: Node | null;
61
+ }
16
62
 
17
63
  export class ParentNode extends ChildNode {
18
- readonly childNodes = new NodeList();
19
- readonly children = new NodeList();
64
+ readonly childNodes = new NodeList<Node>();
65
+ readonly children = new NodeList<Element>();
20
66
 
21
- appendChild(child: Node) {
22
- this.insertInto(child, null);
67
+ appendChild<T extends Node>(child: T) {
68
+ return performWithCustomElementReactions(() =>
69
+ this[INSERT_NODE](child, null),
70
+ );
23
71
  }
24
72
 
25
- insertBefore(child: Node, ref?: Node | null) {
26
- this.insertInto(child, ref || null);
73
+ insertBefore<T extends Node>(child: T, ref?: Node | null) {
74
+ return performWithCustomElementReactions(() =>
75
+ this[INSERT_NODE](child, ref || null),
76
+ );
27
77
  }
28
78
 
29
79
  append(...nodes: (Node | string)[]) {
30
- for (const child of nodes) {
31
- if (child == null) continue;
32
- this.appendChild(toNode(this, child));
33
- }
80
+ return performWithCustomElementReactions(() => {
81
+ const staged = stageNodes(nodes.filter((node) => node != null));
82
+ if (staged.length > 1) validateInsertionNodes(this, staged);
83
+ for (const child of staged) {
84
+ this[INSERT_NODE](toNode(this, child), null);
85
+ }
86
+ });
34
87
  }
35
88
 
36
89
  prepend(...nodes: (Node | string)[]) {
37
- const before = this.firstChild;
38
- for (const child of nodes) {
39
- if (child == null) continue;
40
- this.insertBefore(toNode(this, child), before);
41
- }
90
+ return performWithCustomElementReactions(() => {
91
+ const staged = stageNodes(nodes.filter((node) => node != null));
92
+ if (staged.length > 1) validateInsertionNodes(this, staged);
93
+ const before = this.firstChild;
94
+ for (const child of staged) {
95
+ this[INSERT_NODE](toNode(this, child), before);
96
+ }
97
+ });
42
98
  }
43
99
 
44
100
  replaceChildren(...nodes: (Node | string)[]) {
45
- let child;
46
- while ((child = this.firstChild)) {
47
- this.removeChild(child);
48
- }
49
- this.append(...nodes);
101
+ return performWithCustomElementReactions(() => {
102
+ const staged = stageNodes(nodes.filter((node) => node != null));
103
+ validateInsertionNodes(this, staged);
104
+
105
+ let child;
106
+ while ((child = this.firstChild)) {
107
+ this.removeChildImmediately(child);
108
+ }
109
+ for (const node of staged) {
110
+ this[INSERT_NODE](toNode(this, node), null);
111
+ }
112
+ });
50
113
  }
51
114
 
52
115
  removeChild(child: Node) {
53
- if (child.parentNode !== this) throw Error(`not a child of this node`);
54
- const prev = child[PREV];
55
- const next = child[NEXT];
56
- if (prev) prev[NEXT] = next;
57
- else this[CHILD] = next;
58
- if (next) next[PREV] = prev;
59
-
60
- const childNodes = this.childNodes;
61
-
62
- const childNodesIndex = childNodes.indexOf(child);
63
-
64
- childNodes.splice(childNodesIndex, 1);
116
+ return performWithCustomElementReactions(() => {
117
+ this.removeChildImmediately(child);
118
+ return child;
119
+ });
120
+ }
65
121
 
66
- if (child.nodeType === 1) {
67
- const children = this.children;
68
- children.splice(children.indexOf(child), 1);
122
+ private removeChildImmediately(child: Node) {
123
+ if (child.parentNode !== this) {
124
+ throw createDOMException(
125
+ 'The node is not a child of this node',
126
+ 'NotFoundError',
127
+ );
69
128
  }
129
+ const disconnectedNodes = this[IS_CONNECTED]
130
+ ? selfAndDescendants(child)
131
+ : undefined;
132
+ const previousSibling = child[PREV];
133
+ const nextSibling = child[NEXT];
134
+ const childNodesIndex = detachChild(this, child);
70
135
 
71
- child[PARENT] = null;
72
- child[NEXT] = null;
73
- child[PREV] = null;
74
-
75
- if (this[IS_CONNECTED]) {
76
- for (const node of selfAndDescendants(child)) {
77
- node[IS_CONNECTED] = false;
78
- (node as any).disconnectedCallback?.();
79
- }
136
+ if (disconnectedNodes) {
137
+ for (const node of disconnectedNodes) node[IS_CONNECTED] = false;
80
138
  }
81
139
 
82
- if (this.nodeType === NodeType.ELEMENT_NODE) {
83
- this[HOOKS].removeChild?.(this as any, child as any, childNodesIndex);
140
+ this.queueRemovalMutationRecord(this, child, previousSibling, nextSibling);
141
+
142
+ if (disconnectedNodes) {
143
+ this.enqueueTreeReactions(disconnectedNodes, 'disconnectedCallback');
84
144
  }
145
+ performHookEffects(this.collectRemovalHookEffects(child, childNodesIndex));
85
146
  }
86
147
 
87
148
  replaceChild(newChild: Node, oldChild: Node) {
149
+ return performWithCustomElementReactions(() =>
150
+ this[REPLACE_NODE](newChild, oldChild),
151
+ );
152
+ }
153
+
154
+ [REPLACE_NODE](newChild: Node, oldChild: Node, hookEffects?: HookEffect[]) {
155
+ validateInsertionNodes(this, [newChild]);
88
156
  if (oldChild.parentNode !== this) {
89
- throw Error('reference node is not a child of this parent');
157
+ throw createDOMException(
158
+ 'The reference node is not a child of this parent',
159
+ 'NotFoundError',
160
+ );
90
161
  }
162
+
163
+ const previous = oldChild[PREV];
91
164
  const next = oldChild[NEXT];
92
- this.removeChild(oldChild);
93
- this.insertInto(newChild, next);
165
+ const insertion = this.prepareInsertion(newChild);
166
+ const removedNodes = this[IS_CONNECTED]
167
+ ? selfAndDescendants(oldChild)
168
+ : undefined;
169
+ const insertionRoots = new Set(insertion.map(({node}) => node));
170
+ let before = next;
171
+ while (before && insertionRoots.has(before)) before = before[NEXT];
172
+
173
+ const oldChildIndex = detachChild(this, oldChild);
174
+ if (removedNodes) {
175
+ for (const node of removedNodes) node[IS_CONNECTED] = false;
176
+
177
+ const removedNodeSet = new Set(removedNodes);
178
+ for (const prepared of insertion) {
179
+ if (removedNodeSet.has(prepared.node)) {
180
+ prepared.shouldDisconnect = false;
181
+ }
182
+ }
183
+ }
184
+
185
+ this.commitInsertion(insertion, before);
186
+ const destinationIsConnected = this[IS_CONNECTED];
187
+
188
+ this.queueRemovalMutationRecord(this, oldChild, previous, next);
189
+ this.queueInsertionMutationRecords(insertion);
190
+
191
+ if (removedNodes) {
192
+ this.enqueueTreeReactions(removedNodes, 'disconnectedCallback');
193
+ }
194
+ this.enqueueInsertionReactions(insertion, destinationIsConnected);
195
+ this.performOrCollectHookEffects(
196
+ [
197
+ ...this.collectRemovalHookEffects(oldChild, oldChildIndex),
198
+ ...this.collectInsertionHookEffects(insertion),
199
+ ],
200
+ hookEffects,
201
+ );
202
+
203
+ return oldChild;
94
204
  }
95
205
 
96
206
  querySelectorAll(selector: string) {
@@ -101,29 +211,141 @@ export class ParentNode extends ChildNode {
101
211
  return querySelector(this, selector);
102
212
  }
103
213
 
104
- private insertInto(child: Node, before: Node | null) {
105
- // append the children of a DocumentFragment:
106
- if (child.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE) {
214
+ [INSERT_NODE]<T extends Node>(
215
+ child: T,
216
+ before: Node | null,
217
+ hookEffects?: HookEffect[],
218
+ ) {
219
+ this.validateInsertion(child, before);
220
+ this.insertIntoValidated(child, before, hookEffects);
221
+ return child;
222
+ }
223
+
224
+ [PREFLIGHT_INSERTIONS](children: Node[]) {
225
+ for (const child of children) {
226
+ this.validateInsertion(child, null);
227
+ this.prepareInsertion(child);
228
+ }
229
+ }
230
+
231
+ private validateInsertion(child: Node, before: Node | null) {
232
+ validateInsertionNodes(this, [child]);
233
+
234
+ if (before && before.parentNode !== this) {
235
+ throw createDOMException(
236
+ 'The reference node is not a child of this parent',
237
+ 'NotFoundError',
238
+ );
239
+ }
240
+ }
241
+
242
+ private insertIntoValidated(
243
+ child: Node,
244
+ before: Node | null,
245
+ hookEffects?: HookEffect[],
246
+ ) {
247
+ if (child === before) before = child[NEXT];
248
+
249
+ const insertion = this.prepareInsertion(child);
250
+ this.commitInsertion(insertion, before);
251
+ const destinationIsConnected = this[IS_CONNECTED];
252
+ this.queueInsertionMutationRecords(insertion);
253
+ this.enqueueInsertionReactions(insertion, destinationIsConnected);
254
+ this.performOrCollectHookEffects(
255
+ this.collectInsertionHookEffects(insertion),
256
+ hookEffects,
257
+ );
258
+ }
259
+
260
+ private performOrCollectHookEffects(
261
+ effects: HookEffect[],
262
+ hookEffects?: HookEffect[],
263
+ ) {
264
+ if (hookEffects) hookEffects.push(...effects);
265
+ else performHookEffects(effects);
266
+ }
267
+
268
+ private prepareInsertion(child: Node) {
269
+ const roots: Node[] = [];
270
+
271
+ if (child.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT) {
107
272
  let node = child[CHILD];
108
273
  while (node) {
109
- const next = node[NEXT];
110
- this.insertInto(node, before);
111
- node = next;
274
+ roots.push(node);
275
+ node = node[NEXT];
112
276
  }
113
- return;
277
+ } else {
278
+ roots.push(child);
114
279
  }
115
280
 
116
- if (child.parentNode !== null) {
117
- child.parentNode.removeChild(child);
281
+ const destinationIsConnected = this[IS_CONNECTED];
282
+ const ownerDocument = this[OWNER_DOCUMENT];
283
+ const insertion: PreparedInsertionRoot[] = [];
284
+ for (const node of roots) {
285
+ const wasConnected = node[IS_CONNECTED];
286
+ const shouldTraverse = wasConnected || destinationIsConnected;
287
+ let nodes: Node[] | undefined;
288
+ let adoption: Node[] | undefined;
289
+
290
+ if (node[OWNER_DOCUMENT] === ownerDocument) {
291
+ if (shouldTraverse) nodes = selfAndDescendants(node);
292
+ } else {
293
+ const snapshot = collectAdoptionSnapshot(node);
294
+ adoption = snapshot.nodes;
295
+ if (shouldTraverse) nodes = snapshot.treeNodes;
296
+ }
297
+
298
+ insertion.push({node, nodes, adoption, shouldDisconnect: wasConnected});
118
299
  }
119
300
 
120
- if (before) {
121
- if (before.parentNode !== this) {
122
- throw Error('reference node is not a child of this parent');
301
+ return insertion;
302
+ }
303
+
304
+ private commitInsertion(
305
+ insertion: PreparedInsertionRoot[],
306
+ before: Node | null,
307
+ ) {
308
+ for (const prepared of insertion) {
309
+ const sourceParent = prepared.node[PARENT];
310
+ if (sourceParent) {
311
+ const previousSibling = prepared.node[PREV];
312
+ const nextSibling = prepared.node[NEXT];
313
+ prepared.source = {
314
+ parent: sourceParent,
315
+ index: detachChild(sourceParent, prepared.node),
316
+ previousSibling,
317
+ nextSibling,
318
+ };
319
+ }
320
+ }
321
+
322
+ for (const prepared of insertion) {
323
+ const attached = this.attachChild(prepared.node, before);
324
+ prepared.insertIndex = attached.index;
325
+ prepared.previousSibling = attached.previousSibling;
326
+ prepared.nextSibling = attached.nextSibling;
327
+ }
328
+
329
+ const ownerDocument = this[OWNER_DOCUMENT];
330
+ for (const {adoption} of insertion) {
331
+ if (adoption) adoptNodes(adoption, ownerDocument);
332
+ }
333
+
334
+ const isConnected = this[IS_CONNECTED];
335
+ for (const {nodes} of insertion) {
336
+ if (nodes) {
337
+ for (const node of nodes) node[IS_CONNECTED] = isConnected;
123
338
  }
339
+ }
340
+ }
341
+
342
+ private attachChild(child: Node, before: Node | null) {
343
+ if (before) {
344
+ const previous = before[PREV];
124
345
  child[NEXT] = before;
125
- child[PREV] = before[PREV];
126
- if (before[PREV] === null) this[CHILD] = child;
346
+ child[PREV] = previous;
347
+ if (previous) previous[NEXT] = child;
348
+ else this[CHILD] = child;
127
349
  before[PREV] = child;
128
350
  } else {
129
351
  child[NEXT] = null;
@@ -139,44 +361,234 @@ export class ParentNode extends ChildNode {
139
361
  }
140
362
  }
141
363
 
142
- const ownerDocument = this[OWNER_DOCUMENT];
143
- const isElement = child.nodeType === NodeType.ELEMENT_NODE;
144
-
364
+ const isElement = isElementNode(child);
145
365
  child[PARENT] = this;
146
- child[OWNER_DOCUMENT] = ownerDocument;
147
366
 
148
- const childNodes = this.childNodes;
149
367
  let insertIndex: number;
150
-
151
368
  if (before) {
152
- insertIndex = childNodes.indexOf(before);
153
- childNodes.splice(insertIndex, 0, child);
369
+ insertIndex = this.childNodes.indexOf(before);
370
+ this.childNodes.splice(insertIndex, 0, child);
154
371
 
155
372
  if (isElement) {
156
- const children = this.children;
157
- let ref: Node | null = before;
158
- while (ref && ref.nodeType !== 1) ref = ref[NEXT];
159
- if (ref) {
160
- children.splice(children.indexOf(ref), 0, child);
373
+ let reference: Node | null = before;
374
+ while (reference && !isElementNode(reference)) {
375
+ reference = reference[NEXT];
376
+ }
377
+ if (reference) {
378
+ this.children.splice(this.children.indexOf(reference), 0, child);
161
379
  } else {
162
- children.push(child);
380
+ this.children.push(child);
163
381
  }
164
382
  }
165
383
  } else {
166
- insertIndex = childNodes.length;
167
- childNodes.push(child);
384
+ insertIndex = this.childNodes.length;
385
+ this.childNodes.push(child);
168
386
  if (isElement) this.children.push(child);
169
387
  }
170
388
 
171
- if (this[IS_CONNECTED]) {
172
- for (const node of selfAndDescendants(child)) {
173
- node[IS_CONNECTED] = true;
174
- (node as any).connectedCallback?.();
389
+ return {
390
+ index: insertIndex,
391
+ previousSibling: child[PREV],
392
+ nextSibling: child[NEXT],
393
+ };
394
+ }
395
+
396
+ private queueRemovalMutationRecord(
397
+ parent: ParentNode,
398
+ child: Node,
399
+ previousSibling: Node | null,
400
+ nextSibling: Node | null,
401
+ ) {
402
+ if (!childListObserversActive) return;
403
+
404
+ queueMutationRecord({
405
+ type: 'childList',
406
+ target: parent,
407
+ removedNodes: mutationNodeList(child),
408
+ previousSibling,
409
+ nextSibling,
410
+ });
411
+ }
412
+
413
+ private queueInsertionMutationRecords(insertion: PreparedInsertionRoot[]) {
414
+ if (!childListObserversActive) return;
415
+
416
+ for (const prepared of insertion) {
417
+ const {node, source} = prepared;
418
+ if (source) {
419
+ this.queueRemovalMutationRecord(
420
+ source.parent,
421
+ node,
422
+ source.previousSibling,
423
+ source.nextSibling,
424
+ );
175
425
  }
426
+
427
+ queueMutationRecord({
428
+ type: 'childList',
429
+ target: this,
430
+ addedNodes: mutationNodeList(node),
431
+ previousSibling: prepared.previousSibling,
432
+ nextSibling: prepared.nextSibling,
433
+ });
434
+ }
435
+ }
436
+
437
+ private collectRemovalHookEffects(child: Node, childNodesIndex: number) {
438
+ if (
439
+ this.nodeType !== NODE_TYPE_ELEMENT ||
440
+ isCoveredByPendingPublication(this)
441
+ ) {
442
+ return [];
176
443
  }
177
444
 
178
- if (this.nodeType === NodeType.ELEMENT_NODE) {
179
- this[HOOKS].insertChild?.(this as any, child as any, insertIndex);
445
+ return [
446
+ [
447
+ () =>
448
+ this[HOOKS].removeChild?.(this as any, child as any, childNodesIndex),
449
+ ],
450
+ ] as HookEffect[];
451
+ }
452
+
453
+ private collectInsertionHookEffects(insertion: PreparedInsertionRoot[]) {
454
+ const effects: HookEffect[] = [];
455
+
456
+ for (const prepared of insertion) {
457
+ const {node, source} = prepared;
458
+ if (
459
+ source?.parent.nodeType === NODE_TYPE_ELEMENT &&
460
+ !isCoveredByPendingPublication(source.parent)
461
+ ) {
462
+ effects.push([
463
+ () =>
464
+ source.parent[HOOKS].removeChild?.(
465
+ source.parent as any,
466
+ node as any,
467
+ source.index,
468
+ ),
469
+ ]);
470
+ }
471
+
472
+ if (
473
+ this.nodeType === NODE_TYPE_ELEMENT &&
474
+ !isCoveredByPendingPublication(this)
475
+ ) {
476
+ effects.push([
477
+ () =>
478
+ this[HOOKS].insertChild?.(
479
+ this as any,
480
+ node as any,
481
+ prepared.insertIndex!,
482
+ ),
483
+ node,
484
+ ]);
485
+ }
486
+ }
487
+
488
+ return effects;
489
+ }
490
+
491
+ private enqueueInsertionReactions(
492
+ insertion: PreparedInsertionRoot[],
493
+ destinationIsConnected: boolean,
494
+ ) {
495
+ for (const prepared of insertion) {
496
+ const {nodes} = prepared;
497
+ if (nodes) {
498
+ if (prepared.shouldDisconnect) {
499
+ this.enqueueTreeReactions(nodes, 'disconnectedCallback');
500
+ }
501
+ if (destinationIsConnected) {
502
+ this.enqueueTreeReactions(nodes, 'connectedCallback');
503
+ }
504
+ }
505
+ }
506
+ }
507
+
508
+ private enqueueTreeReactions(
509
+ nodes: Node[],
510
+ callbackName: 'connectedCallback' | 'disconnectedCallback',
511
+ ) {
512
+ enqueueTreeReactions(nodes, callbackName);
513
+ }
514
+ }
515
+
516
+ export function removeChildForAdoption(
517
+ parent: ParentNode,
518
+ child: Node,
519
+ adoption: ReturnType<typeof collectAdoptionSnapshot>,
520
+ destination: Node['ownerDocument'],
521
+ ) {
522
+ if (child.parentNode !== parent) throw Error(`not a child of this node`);
523
+
524
+ const disconnectedNodes = parent[IS_CONNECTED]
525
+ ? adoption.treeNodes
526
+ : undefined;
527
+ const previousSibling = child[PREV];
528
+ const nextSibling = child[NEXT];
529
+ const childNodesIndex = detachChild(parent, child);
530
+
531
+ if (disconnectedNodes) {
532
+ for (const node of disconnectedNodes) node[IS_CONNECTED] = false;
533
+ }
534
+ adoptNodes(adoption.nodes, destination);
535
+
536
+ if (childListObserversActive) {
537
+ queueMutationRecord({
538
+ type: 'childList',
539
+ target: parent,
540
+ removedNodes: mutationNodeList(child),
541
+ previousSibling,
542
+ nextSibling,
543
+ });
544
+ }
545
+
546
+ if (disconnectedNodes) {
547
+ enqueueTreeReactions(disconnectedNodes, 'disconnectedCallback');
548
+ }
549
+
550
+ const effects: HookEffect[] = [];
551
+ if (
552
+ parent.nodeType === NODE_TYPE_ELEMENT &&
553
+ !isCoveredByPendingPublication(parent)
554
+ ) {
555
+ effects.push([
556
+ () =>
557
+ (parent as any)[HOOKS].removeChild?.(parent, child, childNodesIndex),
558
+ ]);
559
+ }
560
+ performHookEffects(effects);
561
+ }
562
+
563
+ function detachChild(parent: ParentNode, child: Node) {
564
+ const previous = child[PREV];
565
+ const next = child[NEXT];
566
+ if (previous) previous[NEXT] = next;
567
+ else parent[CHILD] = next;
568
+ if (next) next[PREV] = previous;
569
+
570
+ const childNodesIndex = parent.childNodes.indexOf(child);
571
+ parent.childNodes.splice(childNodesIndex, 1);
572
+
573
+ if (isElementNode(child)) {
574
+ parent.children.splice(parent.children.indexOf(child), 1);
575
+ }
576
+
577
+ child[PARENT] = null;
578
+ child[NEXT] = null;
579
+ child[PREV] = null;
580
+
581
+ return childNodesIndex;
582
+ }
583
+
584
+ function enqueueTreeReactions(
585
+ nodes: Node[],
586
+ callbackName: 'connectedCallback' | 'disconnectedCallback',
587
+ ) {
588
+ for (const node of nodes) {
589
+ const callback = (node as any)[callbackName];
590
+ if (typeof callback === 'function') {
591
+ enqueueCustomElementReaction(node, () => callback.call(node));
180
592
  }
181
593
  }
182
594
  }
@@ -1,8 +1,8 @@
1
- import {NS, NamespaceURI} from './constants.ts';
1
+ import {NS, SVG_NAMESPACE, type NamespaceURI} from './constants.ts';
2
2
  import {Element} from './Element.ts';
3
3
 
4
4
  export class SVGElement extends Element {
5
- [NS] = NamespaceURI.SVG;
5
+ [NS]: NamespaceURI = SVG_NAMESPACE;
6
6
 
7
7
  get ownerSVGElement() {
8
8
  let root: SVGElement | null = null;