@remix-run/ui 0.1.1 → 0.2.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 (243) hide show
  1. package/README.md +33 -10
  2. package/dist/animation/animate-layout-mixin.js +66 -66
  3. package/dist/animation/animate-layout-mixin.js.map +1 -1
  4. package/dist/animation/spring.d.ts +6 -35
  5. package/dist/animation/spring.js +9 -8
  6. package/dist/animation/spring.js.map +1 -1
  7. package/dist/components/accordion/accordion.d.ts +7 -7
  8. package/dist/components/accordion/accordion.js +0 -2
  9. package/dist/components/accordion/accordion.js.map +1 -1
  10. package/dist/components/accordion/demos/card.demo.d.ts +6 -0
  11. package/dist/components/accordion/demos/card.demo.js +58 -0
  12. package/dist/components/accordion/demos/card.demo.js.map +1 -0
  13. package/dist/components/accordion/demos/multiple.demo.d.ts +6 -0
  14. package/dist/components/accordion/demos/multiple.demo.js +19 -0
  15. package/dist/components/accordion/demos/multiple.demo.js.map +1 -0
  16. package/dist/components/accordion/demos/overview.demo.d.ts +6 -0
  17. package/dist/components/accordion/demos/overview.demo.js +19 -0
  18. package/dist/components/accordion/demos/overview.demo.js.map +1 -0
  19. package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
  20. package/dist/components/breadcrumbs/breadcrumbs.js +0 -2
  21. package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -1
  22. package/dist/components/breadcrumbs/demos/basic.demo.d.ts +5 -0
  23. package/dist/components/breadcrumbs/demos/basic.demo.js +14 -0
  24. package/dist/components/breadcrumbs/demos/basic.demo.js.map +1 -0
  25. package/dist/components/breadcrumbs/demos/separator.demo.d.ts +5 -0
  26. package/dist/components/breadcrumbs/demos/separator.demo.js +14 -0
  27. package/dist/components/breadcrumbs/demos/separator.demo.js.map +1 -0
  28. package/dist/components/button/button.d.ts +86 -4
  29. package/dist/components/button/button.js +63 -3
  30. package/dist/components/button/button.js.map +1 -1
  31. package/dist/components/button/demos/aliases.demo.d.ts +6 -0
  32. package/dist/components/button/demos/aliases.demo.js +18 -0
  33. package/dist/components/button/demos/aliases.demo.js.map +1 -0
  34. package/dist/components/button/demos/basic.demo.d.ts +6 -0
  35. package/dist/components/button/demos/basic.demo.js +19 -0
  36. package/dist/components/button/demos/basic.demo.js.map +1 -0
  37. package/dist/components/button/demos/states.demo.d.ts +6 -0
  38. package/dist/components/button/demos/states.demo.js +29 -0
  39. package/dist/components/button/demos/states.demo.js.map +1 -0
  40. package/dist/components/combobox/combobox.d.ts +11 -11
  41. package/dist/components/combobox/combobox.js +0 -2
  42. package/dist/components/combobox/combobox.js.map +1 -1
  43. package/dist/components/combobox/demos/overview.demo.d.ts +6 -0
  44. package/dist/components/combobox/demos/overview.demo.js +153 -0
  45. package/dist/components/combobox/demos/overview.demo.js.map +1 -0
  46. package/dist/components/listbox/demos/overview.demo.d.ts +6 -0
  47. package/dist/components/listbox/demos/overview.demo.js +64 -0
  48. package/dist/components/listbox/demos/overview.demo.js.map +1 -0
  49. package/dist/components/listbox/listbox.d.ts +4 -9
  50. package/dist/components/listbox/listbox.js +21 -33
  51. package/dist/components/listbox/listbox.js.map +1 -1
  52. package/dist/components/menu/demos/bubbling.demo.d.ts +5 -0
  53. package/dist/components/menu/demos/bubbling.demo.js +15 -0
  54. package/dist/components/menu/demos/bubbling.demo.js.map +1 -0
  55. package/dist/components/menu/demos/overview.demo.d.ts +6 -0
  56. package/dist/components/menu/demos/overview.demo.js +32 -0
  57. package/dist/components/menu/demos/overview.demo.js.map +1 -0
  58. package/dist/components/menu/menu.d.ts +15 -24
  59. package/dist/components/menu/menu.js +23 -38
  60. package/dist/components/menu/menu.js.map +1 -1
  61. package/dist/components/popover/demos/overview.demo.d.ts +6 -0
  62. package/dist/components/popover/demos/overview.demo.js +64 -0
  63. package/dist/components/popover/demos/overview.demo.js.map +1 -0
  64. package/dist/components/popover/popover.d.ts +5 -5
  65. package/dist/components/popover/popover.js.map +1 -1
  66. package/dist/components/select/demos/deconstructed.demo.d.ts +6 -0
  67. package/dist/components/select/demos/deconstructed.demo.js +53 -0
  68. package/dist/components/select/demos/deconstructed.demo.js.map +1 -0
  69. package/dist/components/select/demos/overview.demo.d.ts +5 -0
  70. package/dist/components/select/demos/overview.demo.js +27 -0
  71. package/dist/components/select/demos/overview.demo.js.map +1 -0
  72. package/dist/components/select/select.d.ts +11 -11
  73. package/dist/components/select/select.js +17 -26
  74. package/dist/components/select/select.js.map +1 -1
  75. package/dist/components/tabs/tabs.d.ts +6 -6
  76. package/dist/components/tabs/tabs.js +0 -2
  77. package/dist/components/tabs/tabs.js.map +1 -1
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.js.map +1 -1
  80. package/dist/interactions/keydown/keydown.d.ts +1 -1
  81. package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -1
  82. package/dist/interactions/typeahead/typeahead-mixin.d.ts +3 -1
  83. package/dist/runtime/component.d.ts +18 -12
  84. package/dist/runtime/component.js +93 -80
  85. package/dist/runtime/component.js.map +1 -1
  86. package/dist/runtime/core/attributes.d.ts +14 -0
  87. package/dist/runtime/core/attributes.js +103 -0
  88. package/dist/runtime/core/attributes.js.map +1 -0
  89. package/dist/runtime/core/children.d.ts +7 -0
  90. package/dist/runtime/core/children.js +30 -0
  91. package/dist/runtime/core/children.js.map +1 -0
  92. package/dist/runtime/core/props.d.ts +9 -0
  93. package/dist/runtime/core/props.js +170 -0
  94. package/dist/runtime/core/props.js.map +1 -0
  95. package/dist/runtime/core/vnode.d.ts +3 -0
  96. package/dist/runtime/core/vnode.js +39 -0
  97. package/dist/runtime/core/vnode.js.map +1 -0
  98. package/dist/runtime/create-element.js +2 -1
  99. package/dist/runtime/create-element.js.map +1 -1
  100. package/dist/runtime/diff-dom.js +47 -14
  101. package/dist/runtime/diff-dom.js.map +1 -1
  102. package/dist/runtime/diff-props.d.ts +1 -2
  103. package/dist/runtime/diff-props.js +2 -181
  104. package/dist/runtime/diff-props.js.map +1 -1
  105. package/dist/runtime/dom.d.ts +20 -9
  106. package/dist/runtime/frame.d.ts +23 -2
  107. package/dist/runtime/frame.js +41 -47
  108. package/dist/runtime/frame.js.map +1 -1
  109. package/dist/runtime/jsx.d.ts +1 -1
  110. package/dist/runtime/jsx.js +2 -28
  111. package/dist/runtime/jsx.js.map +1 -1
  112. package/dist/runtime/mixins/link-mixin.d.ts +5 -1
  113. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  114. package/dist/runtime/mixins/mixin.d.ts +2 -1
  115. package/dist/runtime/mixins/mixin.js +9 -4
  116. package/dist/runtime/mixins/mixin.js.map +1 -1
  117. package/dist/runtime/mixins/on-mixin.d.ts +15 -4
  118. package/dist/runtime/mixins/on-mixin.js +9 -2
  119. package/dist/runtime/mixins/on-mixin.js.map +1 -1
  120. package/dist/runtime/mixins/ref-mixin.d.ts +2 -1
  121. package/dist/runtime/mixins/ref-mixin.js.map +1 -1
  122. package/dist/runtime/navigation.js +9 -1
  123. package/dist/runtime/navigation.js.map +1 -1
  124. package/dist/runtime/reconcile.d.ts +1 -1
  125. package/dist/runtime/reconcile.js +452 -197
  126. package/dist/runtime/reconcile.js.map +1 -1
  127. package/dist/runtime/run.d.ts +17 -2
  128. package/dist/runtime/run.js.map +1 -1
  129. package/dist/runtime/scheduler.d.ts +10 -10
  130. package/dist/runtime/scheduler.js +1 -1
  131. package/dist/runtime/scheduler.js.map +1 -1
  132. package/dist/runtime/stream-protocol.d.ts +8 -0
  133. package/dist/runtime/stream-protocol.js +20 -0
  134. package/dist/runtime/stream-protocol.js.map +1 -0
  135. package/dist/runtime/svg-attributes.js +1 -0
  136. package/dist/runtime/svg-attributes.js.map +1 -1
  137. package/dist/runtime/to-vnode.js +7 -11
  138. package/dist/runtime/to-vnode.js.map +1 -1
  139. package/dist/runtime/vnode.d.ts +11 -5
  140. package/dist/runtime/vnode.js +6 -3
  141. package/dist/runtime/vnode.js.map +1 -1
  142. package/dist/server/stream.d.ts +2 -0
  143. package/dist/server/stream.js +154 -114
  144. package/dist/server/stream.js.map +1 -1
  145. package/dist/style/css-mixin.d.ts +2 -1
  146. package/dist/style/css-mixin.js.map +1 -1
  147. package/dist/style/index.d.ts +1 -1
  148. package/dist/style/layers.d.ts +2 -0
  149. package/dist/style/layers.js +3 -0
  150. package/dist/style/layers.js.map +1 -0
  151. package/dist/style/stylesheet.d.ts +13 -11
  152. package/dist/style/stylesheet.js +69 -8
  153. package/dist/style/stylesheet.js.map +1 -1
  154. package/dist/theme/contract.d.ts +24 -222
  155. package/dist/theme/contract.js +2 -2
  156. package/dist/theme/contract.js.map +1 -1
  157. package/dist/theme/glyph-contract.d.ts +1 -3
  158. package/dist/theme/glyph-contract.js.map +1 -1
  159. package/dist/theme/presets/rmx-01/index.d.ts +4 -2
  160. package/dist/theme/presets/rmx-01/index.js.map +1 -1
  161. package/dist/theme/runtime.js +4 -1
  162. package/dist/theme/runtime.js.map +1 -1
  163. package/dist/utils/scroll-lock.d.ts +3 -3
  164. package/dist/utils/scroll-lock.js.map +1 -1
  165. package/package.json +3 -3
  166. package/src/animation/README.md +316 -0
  167. package/src/animation/animate-layout-mixin.ts +79 -71
  168. package/src/animation/spring.ts +21 -9
  169. package/src/components/accordion/README.md +166 -0
  170. package/src/components/accordion/accordion.tsx +14 -14
  171. package/src/components/accordion/demos/card.demo.tsx +113 -0
  172. package/src/components/accordion/demos/multiple.demo.tsx +56 -0
  173. package/src/components/accordion/demos/overview.demo.tsx +56 -0
  174. package/src/components/anchor/README.md +153 -0
  175. package/src/components/anchor/anchor.ts +1 -1
  176. package/src/components/breadcrumbs/README.md +55 -0
  177. package/src/components/breadcrumbs/breadcrumbs.tsx +1 -3
  178. package/src/components/breadcrumbs/demos/basic.demo.tsx +17 -0
  179. package/src/components/breadcrumbs/demos/separator.demo.tsx +18 -0
  180. package/src/components/button/README.md +4 -4
  181. package/src/components/button/button.tsx +118 -20
  182. package/src/components/button/demos/aliases.demo.tsx +27 -0
  183. package/src/components/button/demos/basic.demo.tsx +30 -0
  184. package/src/components/button/demos/states.demo.tsx +52 -0
  185. package/src/components/combobox/README.md +1 -1
  186. package/src/components/combobox/combobox.tsx +37 -21
  187. package/src/components/combobox/demos/overview.demo.tsx +199 -0
  188. package/src/components/glyph/README.md +72 -0
  189. package/src/components/glyph/glyph.tsx +2 -2
  190. package/src/components/listbox/README.md +115 -0
  191. package/src/components/listbox/demos/overview.demo.tsx +101 -0
  192. package/src/components/listbox/listbox.ts +106 -110
  193. package/src/components/menu/README.md +96 -0
  194. package/src/components/menu/demos/bubbling.demo.tsx +34 -0
  195. package/src/components/menu/demos/overview.demo.tsx +76 -0
  196. package/src/components/menu/menu.tsx +61 -55
  197. package/src/components/popover/README.md +3 -3
  198. package/src/components/popover/demos/overview.demo.tsx +143 -0
  199. package/src/components/popover/popover.ts +90 -79
  200. package/src/components/select/README.md +107 -0
  201. package/src/components/select/demos/deconstructed.demo.tsx +95 -0
  202. package/src/components/select/demos/overview.demo.tsx +51 -0
  203. package/src/components/select/select.tsx +53 -36
  204. package/src/components/tabs/tabs.tsx +10 -5
  205. package/src/index.ts +2 -1
  206. package/src/runtime/component.ts +122 -88
  207. package/src/runtime/core/attributes.ts +118 -0
  208. package/src/runtime/core/children.ts +42 -0
  209. package/src/runtime/core/props.ts +187 -0
  210. package/src/runtime/core/vnode.ts +48 -0
  211. package/src/runtime/create-element.ts +2 -1
  212. package/src/runtime/diff-dom.ts +59 -12
  213. package/src/runtime/diff-props.ts +2 -187
  214. package/src/runtime/dom.ts +20 -9
  215. package/src/runtime/frame.ts +71 -55
  216. package/src/runtime/jsx.ts +3 -30
  217. package/src/runtime/mixins/link-mixin.ts +88 -86
  218. package/src/runtime/mixins/mixin.ts +18 -5
  219. package/src/runtime/mixins/on-mixin.ts +20 -12
  220. package/src/runtime/mixins/ref-mixin.ts +4 -5
  221. package/src/runtime/navigation.ts +10 -1
  222. package/src/runtime/reconcile.ts +563 -223
  223. package/src/runtime/run.ts +17 -1
  224. package/src/runtime/scheduler.ts +20 -20
  225. package/src/runtime/stream-protocol.ts +27 -0
  226. package/src/runtime/svg-attributes.ts +1 -0
  227. package/src/runtime/to-vnode.ts +7 -10
  228. package/src/runtime/vnode.ts +16 -9
  229. package/src/server/README.md +90 -0
  230. package/src/server/stream.ts +176 -124
  231. package/src/style/css-mixin.ts +6 -1
  232. package/src/style/index.ts +1 -2
  233. package/src/style/layers.ts +2 -0
  234. package/src/style/stylesheet.ts +88 -11
  235. package/src/test/README.md +107 -0
  236. package/src/test/utils.ts +3 -1
  237. package/src/theme/README.md +103 -0
  238. package/src/theme/contract.ts +37 -5
  239. package/src/theme/glyph-contract.ts +3 -1
  240. package/src/theme/presets/rmx-01/index.ts +4 -2
  241. package/src/theme/runtime.ts +4 -1
  242. package/src/utils/scroll-lock/README.md +33 -0
  243. package/src/utils/scroll-lock.ts +7 -3
@@ -1,16 +1,14 @@
1
1
  import { createComponent, Frame } from "./component.js";
2
2
  import { createFrame } from "./frame.js";
3
3
  import { createRangeRoot } from "./vdom.js";
4
- import { isCommittedComponentNode, isComponentNode, isCommittedHostNode, isCommittedTextNode, isFragmentNode, isHostNode, isTextNode, findContextFromAncestry, } from "./vnode.js";
4
+ import { isCommittedComponentNode, isComponentNode, isCommittedHostNode, isCommittedTextNode, isFragmentNode, isHostNode, isNonRenderNode, isTextNode, findContextFromAncestry, } from "./vnode.js";
5
5
  import { invariant } from "./invariant.js";
6
- import { diffHostProps } from "./diff-props.js";
6
+ import { patchHostProps } from "./core/props.js";
7
7
  import { skipComments, logHydrationMismatch } from "./client-entries.js";
8
8
  import { toVNode } from "./to-vnode.js";
9
9
  import { bindMixinRuntime, cancelPendingMixinRemoval, dispatchMixinBeforeUpdate, dispatchMixinCommit, getMixinRuntimeSignal, prepareMixinRemoval, resolveMixedProps, teardownMixins, } from "./mixins/mixin.js";
10
+ import { isOnMixinDescriptor } from "./mixins/on-mixin.js";
10
11
  const SVG_NS = 'http://www.w3.org/2000/svg';
11
- // Internal diffing flags (modeled after Preact)
12
- const INSERT_VNODE = 1 << 0;
13
- const MATCHED = 1 << 1;
14
12
  let idCounter = 0;
15
13
  let persistedRemovalToken = 0;
16
14
  const persistedMixinNodes = new Set();
@@ -60,6 +58,7 @@ function findMatchingPersistedMixinNode(type, key, domParent) {
60
58
  }
61
59
  return null;
62
60
  }
61
+ const EMPTY_DIRECT_EVENT_DESCRIPTORS = [];
63
62
  function shouldRestoreControlledReflectionOnInput(node, state) {
64
63
  // Some controls dispatch `input` before `change` for the same interaction.
65
64
  // When checked/value state is typically handled on `change`, restoring on the
@@ -179,6 +178,17 @@ function setPropertyReflection(element, key, value) {
179
178
  }
180
179
  function resolveNodeMixProps(node, frame, scheduler, state) {
181
180
  let mix = node.props.mix;
181
+ let directEventDescriptors = resolveDirectEventDescriptors(mix);
182
+ if (directEventDescriptors) {
183
+ if (state) {
184
+ teardownMixins(state);
185
+ }
186
+ node._mixState = undefined;
187
+ node._mixedProps = node.props;
188
+ node._directEventDescriptors = directEventDescriptors;
189
+ return node.props;
190
+ }
191
+ node._directEventDescriptors = undefined;
182
192
  if (state == null && (mix == null || (Array.isArray(mix) && mix.length === 0))) {
183
193
  node._mixState = undefined;
184
194
  node._mixedProps = node.props;
@@ -201,6 +211,21 @@ function resolveNodeMixProps(node, frame, scheduler, state) {
201
211
  node._mixedProps = resolved.props;
202
212
  return resolved.props;
203
213
  }
214
+ function resolveDirectEventDescriptors(mix) {
215
+ if (!mix)
216
+ return EMPTY_DIRECT_EVENT_DESCRIPTORS;
217
+ if (!Array.isArray(mix)) {
218
+ return isOnMixinDescriptor(mix) ? [mix] : null;
219
+ }
220
+ return areOnMixinDescriptors(mix) ? mix : null;
221
+ }
222
+ function areOnMixinDescriptors(descriptors) {
223
+ for (let item of descriptors) {
224
+ if (!isOnMixinDescriptor(item))
225
+ return false;
226
+ }
227
+ return true;
228
+ }
204
229
  function enqueueMixinBindingUpdate(done) {
205
230
  let node = this.target;
206
231
  let state = node._mixState;
@@ -213,7 +238,7 @@ function enqueueMixinBindingUpdate(done) {
213
238
  dispatchMixinBeforeUpdate(state);
214
239
  let prevProps = getHostProps(node);
215
240
  let nextProps = resolveNodeMixProps(node, this.frame, this.scheduler, state);
216
- diffHostProps(prevProps, nextProps, this.node);
241
+ patchHostProps(prevProps, nextProps, this.node);
217
242
  dispatchMixinCommit(state);
218
243
  done(state ? getMixinRuntimeSignal(state) : AbortSignal.abort());
219
244
  },
@@ -258,6 +283,9 @@ export function diffVNodes(curr, next, domParent, frame, scheduler, styles, vPar
258
283
  replace(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, anchor);
259
284
  return rootCursor;
260
285
  }
286
+ if (isNonRenderNode(curr) && isNonRenderNode(next)) {
287
+ return rootCursor;
288
+ }
261
289
  if (isCommittedTextNode(curr) && isTextNode(next)) {
262
290
  diffText(curr, next, vParent);
263
291
  return rootCursor;
@@ -271,7 +299,7 @@ export function diffVNodes(curr, next, domParent, frame, scheduler, styles, vPar
271
299
  return rootCursor;
272
300
  }
273
301
  if (isFragmentNode(curr) && isFragmentNode(next)) {
274
- diffChildren(curr._children, next._children, domParent, frame, scheduler, styles, vParent, rootTarget, undefined, anchor);
302
+ diffChildren(curr._children, next._children, domParent, frame, scheduler, styles, next, rootTarget, undefined, anchor);
275
303
  return rootCursor;
276
304
  }
277
305
  if (curr.type === Frame && next.type === Frame) {
@@ -294,7 +322,7 @@ function replace(curr, next, domParent, frame, scheduler, styles, vParent, rootT
294
322
  }
295
323
  return;
296
324
  }
297
- let replacementAnchor = findNextSiblingDomAnchor(curr, vParent) ?? anchor;
325
+ let replacementAnchor = findNextSiblingDomAnchor(curr) ?? anchor;
298
326
  remove(curr, domParent, scheduler, styles);
299
327
  insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor);
300
328
  }
@@ -319,16 +347,20 @@ function diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget) {
319
347
  curr._dom.innerHTML = '';
320
348
  }
321
349
  diffChildren(curr._children, next._children, curr._dom, frame, scheduler, styles, next, rootTarget);
322
- diffHostProps(currProps, nextProps, curr._dom);
350
+ patchHostProps(currProps, nextProps, curr._dom);
323
351
  next._dom = curr._dom;
324
352
  next._parent = vParent;
325
353
  next._controller = curr._controller;
354
+ next._directEventState = curr._directEventState;
326
355
  next._controlledState = curr._controlledState;
356
+ syncDirectEventListeners(next);
327
357
  if (next._controlledState || shouldTrackControlledReflection(nextProps)) {
328
358
  ensureControlledReflection(next, scheduler);
329
359
  syncControlledReflection(next, nextProps);
330
360
  }
331
- bindNodeMixRuntime(next, frame, scheduler, styles);
361
+ if (next._mixState) {
362
+ bindNodeMixRuntime(next, frame, scheduler, styles);
363
+ }
332
364
  if (shouldDispatchMixinLifecycle) {
333
365
  scheduler.enqueueCommitPhase([() => dispatchMixinCommit(nextMixState)]);
334
366
  }
@@ -338,11 +370,94 @@ function setupHostNode(node, dom, scheduler) {
338
370
  node._dom = dom;
339
371
  let props = getHostProps(node);
340
372
  let committedNode = node;
373
+ syncDirectEventListeners(committedNode);
341
374
  if (shouldTrackControlledReflection(props)) {
342
375
  ensureControlledReflection(committedNode, scheduler);
343
376
  syncControlledReflection(committedNode, props);
344
377
  }
345
378
  }
379
+ function syncDirectEventListeners(node) {
380
+ let descriptors = node._directEventDescriptors;
381
+ if (!descriptors) {
382
+ teardownDirectEventListeners(node);
383
+ return;
384
+ }
385
+ if (descriptors.length === 0) {
386
+ teardownDirectEventListeners(node);
387
+ return;
388
+ }
389
+ let state = node._directEventState;
390
+ if (!state) {
391
+ state = { bindings: [] };
392
+ node._directEventState = state;
393
+ }
394
+ let bindings = state.bindings;
395
+ for (let index = 0; index < descriptors.length; index++) {
396
+ let descriptor = descriptors[index];
397
+ let [type, handler, captureBoolean = false] = descriptor.args;
398
+ let binding = bindings[index];
399
+ if (!binding) {
400
+ binding = createDirectEventBinding(type, handler, captureBoolean);
401
+ bindings[index] = binding;
402
+ attachDirectEventBinding(node._dom, binding);
403
+ continue;
404
+ }
405
+ if (binding.type !== type || binding.capture !== captureBoolean) {
406
+ removeDirectEventBinding(node._dom, binding);
407
+ binding.type = type;
408
+ binding.capture = captureBoolean;
409
+ attachDirectEventBinding(node._dom, binding);
410
+ }
411
+ binding.handler = handler;
412
+ }
413
+ for (let index = descriptors.length; index < bindings.length; index++) {
414
+ removeDirectEventBinding(node._dom, bindings[index]);
415
+ }
416
+ bindings.length = descriptors.length;
417
+ }
418
+ function createDirectEventBinding(type, handler, capture) {
419
+ let binding = {
420
+ type,
421
+ handler,
422
+ capture,
423
+ reentry: null,
424
+ stableHandler: null,
425
+ };
426
+ return binding;
427
+ }
428
+ function getStableDirectEventHandler(binding) {
429
+ if (binding.stableHandler)
430
+ return binding.stableHandler;
431
+ binding.stableHandler = (event) => {
432
+ invokeDirectEventBinding(binding, event);
433
+ };
434
+ return binding.stableHandler;
435
+ }
436
+ function attachDirectEventBinding(dom, binding) {
437
+ dom.addEventListener(binding.type, getStableDirectEventHandler(binding), binding.capture);
438
+ }
439
+ function removeDirectEventBinding(dom, binding) {
440
+ if (binding.stableHandler) {
441
+ dom.removeEventListener(binding.type, binding.stableHandler, binding.capture);
442
+ }
443
+ binding.reentry?.abort(new DOMException('', 'AbortError'));
444
+ binding.reentry = null;
445
+ }
446
+ function teardownDirectEventListeners(node) {
447
+ let state = node._directEventState;
448
+ if (!state)
449
+ return;
450
+ for (let binding of state.bindings) {
451
+ removeDirectEventBinding(node._dom, binding);
452
+ }
453
+ state.bindings.length = 0;
454
+ node._directEventState = undefined;
455
+ }
456
+ function invokeDirectEventBinding(binding, event) {
457
+ binding.reentry?.abort(new DOMException('', 'EventReentry'));
458
+ binding.reentry = new AbortController();
459
+ void binding.handler(event, binding.reentry.signal);
460
+ }
346
461
  function diffText(curr, next, vParent) {
347
462
  if (curr._text !== next._text) {
348
463
  curr._dom.textContent = next._text;
@@ -369,6 +484,9 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
369
484
  let doInsert = anchor
370
485
  ? (dom) => domParent.insertBefore(dom, anchor)
371
486
  : (dom) => domParent.appendChild(dom);
487
+ if (isNonRenderNode(node)) {
488
+ return cursor;
489
+ }
372
490
  if (isTextNode(node)) {
373
491
  if (cursor instanceof Text) {
374
492
  node._parent = vParent;
@@ -414,9 +532,11 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
414
532
  }
415
533
  // Render explicit <head> children directly into document.head.
416
534
  diffChildren(null, node._children, targetHead, frame, scheduler, styles, node, rootTarget, childCursor);
417
- diffHostProps({}, hostProps, targetHead);
535
+ patchHostProps({}, hostProps, targetHead);
418
536
  setupHostNode(node, targetHead, scheduler);
419
- bindNodeMixRuntime(node, frame, scheduler, styles);
537
+ if (node._mixState) {
538
+ bindNodeMixRuntime(node, frame, scheduler, styles);
539
+ }
420
540
  return cursor;
421
541
  }
422
542
  }
@@ -432,7 +552,7 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
432
552
  let cursorTag = node._svg ? cursor.tagName : cursor.tagName.toLowerCase();
433
553
  if (cursorTag === node.type) {
434
554
  let nextCursor = cursor.nextSibling;
435
- diffHostProps({}, hostProps, cursor);
555
+ patchHostProps({}, hostProps, cursor);
436
556
  // Handle innerHTML prop
437
557
  if (hostProps.innerHTML != null) {
438
558
  cursor.innerHTML = hostProps.innerHTML;
@@ -443,7 +563,9 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
443
563
  diffChildren(null, node._children, cursor, frame, scheduler, styles, node, rootTarget, childCursor);
444
564
  }
445
565
  setupHostNode(node, cursor, scheduler);
446
- bindNodeMixRuntime(node, frame, scheduler, styles);
566
+ if (node._mixState) {
567
+ bindNodeMixRuntime(node, frame, scheduler, styles);
568
+ }
447
569
  return nextCursor;
448
570
  }
449
571
  else {
@@ -455,7 +577,7 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
455
577
  if (nextTag === node.type) {
456
578
  let nextCursor = nextSibling.nextSibling;
457
579
  // Found a match after skipping - adopt it and leave skipped node in place
458
- diffHostProps({}, hostProps, nextSibling);
580
+ patchHostProps({}, hostProps, nextSibling);
459
581
  if (hostProps.innerHTML != null) {
460
582
  nextSibling.innerHTML = hostProps.innerHTML;
461
583
  }
@@ -464,7 +586,9 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
464
586
  diffChildren(null, node._children, nextSibling, frame, scheduler, styles, node, rootTarget, childCursor);
465
587
  }
466
588
  setupHostNode(node, nextSibling, scheduler);
467
- bindNodeMixRuntime(node, frame, scheduler, styles);
589
+ if (node._mixState) {
590
+ bindNodeMixRuntime(node, frame, scheduler, styles);
591
+ }
468
592
  return nextCursor;
469
593
  }
470
594
  }
@@ -476,7 +600,7 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
476
600
  let dom = node._svg
477
601
  ? document.createElementNS(SVG_NS, node.type)
478
602
  : document.createElement(node.type);
479
- diffHostProps({}, hostProps, dom);
603
+ patchHostProps({}, hostProps, dom);
480
604
  // Handle innerHTML prop
481
605
  if (hostProps.innerHTML != null) {
482
606
  dom.innerHTML = hostProps.innerHTML;
@@ -485,14 +609,16 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
485
609
  diffChildren(null, node._children, dom, frame, scheduler, styles, node, rootTarget);
486
610
  }
487
611
  setupHostNode(node, dom, scheduler);
488
- bindNodeMixRuntime(node, frame, scheduler, styles, false, domParent);
612
+ if (node._mixState) {
613
+ bindNodeMixRuntime(node, frame, scheduler, styles, false, domParent);
614
+ }
489
615
  doInsert(dom);
490
616
  return cursor;
491
617
  }
492
618
  if (isFragmentNode(node)) {
493
619
  // Insert fragment children in order before the same anchor
494
620
  for (let child of node._children) {
495
- cursor = insert(child, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
621
+ cursor = insert(child, domParent, frame, scheduler, styles, node, rootTarget, anchor, cursor);
496
622
  }
497
623
  return cursor;
498
624
  }
@@ -783,6 +909,8 @@ function findFrameEndComment(start) {
783
909
  return null;
784
910
  }
785
911
  export function renderComponent(handle, currContent, next, domParent, frame, scheduler, styles, rootTarget, vParent, anchor, cursor) {
912
+ if (handle.isRemoved())
913
+ return cursor;
786
914
  let [element, tasks] = handle.render(next.props);
787
915
  let content = toVNode(element);
788
916
  let newCursor = diffVNodes(currContent, content, domParent, frame, scheduler, styles, next, rootTarget, anchor, cursor);
@@ -835,6 +963,7 @@ function cleanupDescendants(node, scheduler, styles) {
835
963
  cleanupDescendants(child, scheduler, styles);
836
964
  }
837
965
  teardownMixins(node._mixState);
966
+ teardownDirectEventListeners(node);
838
967
  teardownControlledReflection(node);
839
968
  if (node._controller)
840
969
  node._controller.abort();
@@ -916,6 +1045,7 @@ function performHostNodeRemoval(node, domParent, scheduler, styles) {
916
1045
  }
917
1046
  }
918
1047
  teardownMixins(node._mixState);
1048
+ teardownDirectEventListeners(node);
919
1049
  teardownControlledReflection(node);
920
1050
  // Never remove the real document.head node when reconciling a <head> vnode.
921
1051
  if (!isHeadHostNode(node)) {
@@ -925,55 +1055,35 @@ function performHostNodeRemoval(node, domParent, scheduler, styles) {
925
1055
  node._controller.abort();
926
1056
  }
927
1057
  function diffChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor) {
928
- let nextLength = next.length;
929
- // Warn when duplicate keys are present among siblings. Duplicate keys are
930
- // still processed (last one wins), but they make keyed diffing ambiguous.
931
- let hasKeys = false;
932
- let seenKeys;
933
- let duplicateKeys;
934
- for (let i = 0; i < nextLength; i++) {
935
- let node = next[i];
936
- if (node && node.key != null) {
937
- hasKeys = true;
938
- if (!seenKeys) {
939
- seenKeys = new Set([node.key]);
940
- continue;
941
- }
942
- if (seenKeys.has(node.key)) {
943
- if (!duplicateKeys) {
944
- duplicateKeys = new Set();
945
- }
946
- duplicateKeys.add(node.key);
947
- }
948
- else {
949
- seenKeys.add(node.key);
950
- }
951
- }
952
- }
953
- if (duplicateKeys?.size) {
954
- let quotedKeys = Array.from(duplicateKeys, (key) => `"${key}"`);
955
- console.warn(`Duplicate keys detected in siblings: ${quotedKeys.join(', ')}. Keys should be unique.`);
956
- }
957
- // Initial mount / hydration: delegate to insert() for each child so that
958
- // hydration cursors and creation logic remain centralized there.
1058
+ let hasKeys = hasKeyedChildren(next);
959
1059
  if (curr === null) {
1060
+ if (hasKeys) {
1061
+ warnDuplicateKeys(next);
1062
+ }
960
1063
  for (let node of next) {
961
1064
  cursor = insert(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
962
1065
  }
963
1066
  vParent._children = next;
964
1067
  return cursor;
965
1068
  }
966
- let currLength = curr.length;
967
- // Detect if any keys are present in the new children. If not, we can fall
968
- // back to the simpler index-based diff which is cheaper and matches
969
- // pre-existing behavior.
1069
+ if (next.length === 0 &&
1070
+ anchor === undefined &&
1071
+ !parentUsesInnerHTML(vParent) &&
1072
+ canBulkClearChildren(curr)) {
1073
+ for (let node of curr) {
1074
+ cleanupDescendants(node, scheduler, styles);
1075
+ }
1076
+ domParent.textContent = '';
1077
+ vParent._children = next;
1078
+ return;
1079
+ }
970
1080
  if (!hasKeys) {
971
- for (let i = 0; i < nextLength; i++) {
972
- let currentNode = i < currLength ? curr[i] : null;
1081
+ for (let i = 0; i < next.length; i++) {
1082
+ let currentNode = i < curr.length ? curr[i] : null;
973
1083
  diffVNodes(currentNode, next[i], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
974
1084
  }
975
- if (currLength > nextLength) {
976
- for (let i = nextLength; i < currLength; i++) {
1085
+ if (curr.length > next.length) {
1086
+ for (let i = next.length; i < curr.length; i++) {
977
1087
  let node = curr[i];
978
1088
  if (node)
979
1089
  remove(node, domParent, scheduler, styles);
@@ -982,167 +1092,302 @@ function diffChildren(curr, next, domParent, frame, scheduler, styles, vParent,
982
1092
  vParent._children = next;
983
1093
  return;
984
1094
  }
985
- if (currLength === nextLength) {
986
- let canDiffByPosition = true;
987
- for (let i = 0; i < nextLength; i++) {
988
- let currentNode = curr[i];
989
- let nextNode = next[i];
990
- if (!currentNode || currentNode.key !== nextNode.key || currentNode.type !== nextNode.type) {
991
- canDiffByPosition = false;
992
- break;
993
- }
994
- }
995
- if (canDiffByPosition) {
996
- for (let i = 0; i < nextLength; i++) {
997
- diffVNodes(curr[i], next[i], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
998
- }
999
- vParent._children = next;
1000
- return;
1001
- }
1095
+ patchKeyedChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor);
1096
+ return;
1097
+ }
1098
+ function parentUsesInnerHTML(parent) {
1099
+ return isHostNode(parent) && getHostProps(parent).innerHTML != null;
1100
+ }
1101
+ function canBulkClearChildren(children) {
1102
+ for (let child of children) {
1103
+ if (!canBulkClearNode(child))
1104
+ return false;
1002
1105
  }
1003
- // --- O(n + m) keyed diff with Map-based lookup ------------------------------
1004
- let oldChildren = curr;
1005
- let oldChildrenLength = currLength;
1006
- let remainingOldChildren = oldChildrenLength;
1007
- // Build key → index map for O(1) lookup: O(m)
1008
- let oldKeyMap = new Map();
1009
- for (let i = 0; i < oldChildrenLength; i++) {
1010
- let c = oldChildren[i];
1011
- if (c) {
1012
- c._flags = 0;
1013
- if (c.key != null) {
1014
- oldKeyMap.set(c.key, i);
1015
- }
1106
+ return true;
1107
+ }
1108
+ function canBulkClearNode(node) {
1109
+ if (isCommittedTextNode(node))
1110
+ return true;
1111
+ if (isCommittedHostNode(node)) {
1112
+ if (node._mixState)
1113
+ return false;
1114
+ for (let child of node._children) {
1115
+ if (!canBulkClearNode(child))
1116
+ return false;
1016
1117
  }
1118
+ return true;
1017
1119
  }
1018
- let skew = 0;
1019
- let newChildren = new Array(nextLength);
1020
- // First pass: match new children to old ones using Map lookup: O(n)
1021
- for (let i = 0; i < nextLength; i++) {
1022
- let childVNode = next[i];
1023
- if (!childVNode) {
1024
- newChildren[i] = childVNode;
1120
+ if (isFragmentNode(node)) {
1121
+ return canBulkClearChildren(node._children);
1122
+ }
1123
+ if (isCommittedComponentNode(node)) {
1124
+ return canBulkClearNode(node._content);
1125
+ }
1126
+ return false;
1127
+ }
1128
+ function hasKeyedChildren(children) {
1129
+ for (let node of children) {
1130
+ if (node.key != null)
1131
+ return true;
1132
+ }
1133
+ return false;
1134
+ }
1135
+ function warnDuplicateKeys(children) {
1136
+ let seenKeys;
1137
+ let duplicateKeys;
1138
+ for (let node of children) {
1139
+ if (node.key == null)
1140
+ continue;
1141
+ if (!seenKeys) {
1142
+ seenKeys = new Set([node.key]);
1025
1143
  continue;
1026
1144
  }
1027
- newChildren[i] = childVNode;
1028
- childVNode._parent = vParent;
1029
- let skewedIndex = i + skew;
1030
- let matchingIndex = -1;
1031
- let key = childVNode.key;
1032
- let type = childVNode.type;
1033
- if (key != null) {
1034
- // O(1) Map lookup for keyed children
1035
- let mapIndex = oldKeyMap.get(key);
1036
- if (mapIndex !== undefined) {
1037
- let candidate = oldChildren[mapIndex];
1038
- let candidateFlags = candidate?._flags ?? 0;
1039
- if (candidate && (candidateFlags & MATCHED) === 0 && candidate.type === type) {
1040
- matchingIndex = mapIndex;
1041
- }
1042
- }
1145
+ if (seenKeys.has(node.key)) {
1146
+ duplicateKeys ??= new Set();
1147
+ duplicateKeys.add(node.key);
1043
1148
  }
1044
1149
  else {
1045
- // Non-keyed children use positional identity only - no searching
1046
- let searchVNode = oldChildren[skewedIndex];
1047
- let searchFlags = searchVNode?._flags ?? 0;
1048
- let available = searchVNode != null && (searchFlags & MATCHED) === 0;
1049
- if (available && searchVNode.key == null && type === searchVNode.type) {
1050
- matchingIndex = skewedIndex;
1150
+ seenKeys.add(node.key);
1151
+ }
1152
+ }
1153
+ if (duplicateKeys?.size) {
1154
+ let quotedKeys = Array.from(duplicateKeys, (key) => `"${key}"`);
1155
+ console.warn(`Duplicate keys detected in siblings: ${quotedKeys.join(', ')}. Keys should be unique.`);
1156
+ }
1157
+ }
1158
+ function patchKeyedChildren(curr, next, domParent, frame, scheduler, styles, vParent, rootTarget, cursor, anchor) {
1159
+ let matches = matchKeyedChildrenInOrder(curr, next) ??
1160
+ matchKeyedChildrenAfterSingleRemoval(curr, next) ??
1161
+ matchKeyedChildrenAfterPairSwap(curr, next);
1162
+ if (!matches) {
1163
+ warnDuplicateKeys(next);
1164
+ matches = matchKeyedChildren(curr, next);
1165
+ }
1166
+ let matchAnalysis = analyzeKeyedChildMatches(curr.length, matches);
1167
+ if (matchAnalysis.hasRemovals) {
1168
+ let usedOldIndexes = new Uint8Array(curr.length);
1169
+ for (let match of matches) {
1170
+ if (match.oldIndex >= 0) {
1171
+ usedOldIndexes[match.oldIndex] = 1;
1051
1172
  }
1052
1173
  }
1053
- childVNode._index = matchingIndex;
1054
- let matchedOldVNode = null;
1055
- if (matchingIndex !== -1) {
1056
- matchedOldVNode = oldChildren[matchingIndex];
1057
- remainingOldChildren--;
1058
- if (matchedOldVNode) {
1059
- matchedOldVNode._flags = (matchedOldVNode._flags ?? 0) | MATCHED;
1174
+ for (let oldIndex = 0; oldIndex < curr.length; oldIndex++) {
1175
+ if (usedOldIndexes[oldIndex] === 0) {
1176
+ remove(curr[oldIndex], domParent, scheduler, styles);
1060
1177
  }
1061
1178
  }
1062
- // Determine whether this is a mount vs move and mark INSERT_VNODE
1063
- let oldDom = matchedOldVNode && findFirstDomAnchor(matchedOldVNode);
1064
- let isMounting = !matchedOldVNode || !oldDom;
1065
- if (isMounting) {
1066
- if (matchingIndex === -1) {
1067
- // Adjust skew similar to Preact when lengths differ
1068
- if (nextLength > oldChildrenLength) {
1069
- skew--;
1070
- }
1071
- else if (nextLength < oldChildrenLength) {
1072
- skew++;
1179
+ }
1180
+ vParent._children = next;
1181
+ for (let index = 0; index < next.length; index++) {
1182
+ let match = matches[index];
1183
+ let oldNode = match.oldIndex >= 0 ? curr[match.oldIndex] : null;
1184
+ diffVNodes(oldNode, next[index], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
1185
+ }
1186
+ if (matchAnalysis.canSkipPlacement) {
1187
+ return;
1188
+ }
1189
+ let stableIndexes = lisMatches(matches);
1190
+ let stableCursor = stableIndexes.length - 1;
1191
+ let placementAnchor = anchor ?? null;
1192
+ for (let index = next.length - 1; index >= 0; index--) {
1193
+ let nextNode = next[index];
1194
+ let isStable = stableIndexes[stableCursor] === index;
1195
+ if (isStable) {
1196
+ stableCursor--;
1197
+ }
1198
+ else {
1199
+ placeVNode(nextNode, domParent, placementAnchor);
1200
+ }
1201
+ placementAnchor = findFirstDomAnchor(nextNode) ?? placementAnchor;
1202
+ }
1203
+ }
1204
+ function matchKeyedChildren(curr, next) {
1205
+ let oldKeyMap = new Map();
1206
+ let usedOldIndexes = new Set();
1207
+ let unkeyedSearchStart = 0;
1208
+ for (let index = 0; index < curr.length; index++) {
1209
+ let key = curr[index].key;
1210
+ if (key != null)
1211
+ oldKeyMap.set(key, index);
1212
+ }
1213
+ return next.map((nextNode) => {
1214
+ let oldIndex = -1;
1215
+ if (nextNode.key != null) {
1216
+ let keyedOldIndex = oldKeyMap.get(nextNode.key);
1217
+ if (keyedOldIndex !== undefined) {
1218
+ let oldNode = curr[keyedOldIndex];
1219
+ if (!usedOldIndexes.has(keyedOldIndex) && oldNode.type === nextNode.type) {
1220
+ oldIndex = keyedOldIndex;
1073
1221
  }
1074
1222
  }
1075
- childVNode._flags = (childVNode._flags ?? 0) | INSERT_VNODE;
1076
1223
  }
1077
- else if (matchingIndex !== i + skew) {
1078
- if (matchingIndex === i + skew - 1) {
1079
- skew--;
1080
- }
1081
- else if (matchingIndex === i + skew + 1) {
1082
- skew++;
1083
- }
1084
- else {
1085
- if (matchingIndex > i + skew)
1086
- skew--;
1087
- else
1088
- skew++;
1089
- childVNode._flags = (childVNode._flags ?? 0) | INSERT_VNODE;
1224
+ else {
1225
+ for (let index = unkeyedSearchStart; index < curr.length; index++) {
1226
+ let oldNode = curr[index];
1227
+ if (usedOldIndexes.has(index) || oldNode.key != null || oldNode.type !== nextNode.type) {
1228
+ continue;
1229
+ }
1230
+ oldIndex = index;
1231
+ unkeyedSearchStart = index + 1;
1232
+ break;
1090
1233
  }
1091
1234
  }
1235
+ if (oldIndex >= 0)
1236
+ usedOldIndexes.add(oldIndex);
1237
+ return { oldIndex };
1238
+ });
1239
+ }
1240
+ function matchKeyedChildrenInOrder(curr, next) {
1241
+ let length = Math.min(curr.length, next.length);
1242
+ let matches = [];
1243
+ for (let index = 0; index < length; index++) {
1244
+ let nextNode = next[index];
1245
+ if (nextNode.key == null)
1246
+ return null;
1247
+ let oldNode = curr[index];
1248
+ if (oldNode.key !== nextNode.key || oldNode.type !== nextNode.type) {
1249
+ return null;
1250
+ }
1251
+ matches.push({ oldIndex: index });
1092
1252
  }
1093
- // Unmount any old children that weren't matched
1094
- if (remainingOldChildren) {
1095
- for (let i = 0; i < oldChildrenLength; i++) {
1096
- let oldVNode = oldChildren[i];
1097
- if (oldVNode && ((oldVNode._flags ?? 0) & MATCHED) === 0) {
1098
- remove(oldVNode, domParent, scheduler, styles);
1099
- }
1253
+ for (let index = length; index < next.length; index++) {
1254
+ if (next[index].key == null)
1255
+ return null;
1256
+ matches.push({ oldIndex: -1 });
1257
+ }
1258
+ return matches;
1259
+ }
1260
+ function matchKeyedChildrenAfterSingleRemoval(curr, next) {
1261
+ if (curr.length !== next.length + 1)
1262
+ return null;
1263
+ let matches = [];
1264
+ let oldIndex = 0;
1265
+ let skippedOldNode = false;
1266
+ for (let nextIndex = 0; nextIndex < next.length; nextIndex++) {
1267
+ let nextNode = next[nextIndex];
1268
+ if (nextNode.key == null)
1269
+ return null;
1270
+ let oldNode = curr[oldIndex];
1271
+ if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
1272
+ matches.push({ oldIndex });
1273
+ oldIndex++;
1274
+ continue;
1275
+ }
1276
+ if (skippedOldNode)
1277
+ return null;
1278
+ skippedOldNode = true;
1279
+ oldIndex++;
1280
+ oldNode = curr[oldIndex];
1281
+ if (oldNode.key !== nextNode.key || oldNode.type !== nextNode.type) {
1282
+ return null;
1100
1283
  }
1284
+ matches.push({ oldIndex });
1285
+ oldIndex++;
1101
1286
  }
1102
- // Second pass: diff matched pairs and place/move DOM nodes in the correct
1103
- // order, similar to Preact's diffChildren + insert.
1104
- vParent._children = newChildren;
1105
- let lastPlaced = null;
1106
- for (let i = 0; i < nextLength; i++) {
1107
- let childVNode = newChildren[i];
1108
- if (!childVNode)
1287
+ return matches;
1288
+ }
1289
+ function matchKeyedChildrenAfterPairSwap(curr, next) {
1290
+ if (curr.length !== next.length)
1291
+ return null;
1292
+ let matches = [];
1293
+ let firstMismatch = -1;
1294
+ let secondMismatch = -1;
1295
+ for (let index = 0; index < next.length; index++) {
1296
+ let nextNode = next[index];
1297
+ if (nextNode.key == null)
1298
+ return null;
1299
+ let oldNode = curr[index];
1300
+ if (oldNode.key === nextNode.key && oldNode.type === nextNode.type) {
1301
+ matches.push({ oldIndex: index });
1109
1302
  continue;
1110
- let idx = childVNode._index ?? -1;
1111
- let oldVNode = idx >= 0 ? oldChildren[idx] : null;
1112
- diffVNodes(oldVNode, childVNode, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
1113
- let shouldPlace = (childVNode._flags ?? 0) & INSERT_VNODE;
1114
- let firstDom = findFirstDomAnchor(childVNode);
1115
- let lastDom = firstDom ? findLastDomAnchor(childVNode) : null;
1116
- if (shouldPlace && firstDom && lastDom && firstDom.parentNode === domParent) {
1117
- let target;
1118
- if (lastPlaced === null) {
1119
- if (vParent._rangeStart && vParent._rangeStart.parentNode === domParent) {
1120
- target = vParent._rangeStart.nextSibling;
1121
- }
1122
- else {
1123
- target = domParent.firstChild;
1124
- }
1303
+ }
1304
+ if (firstMismatch === -1) {
1305
+ firstMismatch = index;
1306
+ }
1307
+ else if (secondMismatch === -1) {
1308
+ secondMismatch = index;
1309
+ }
1310
+ else {
1311
+ return null;
1312
+ }
1313
+ matches.push({ oldIndex: -1 });
1314
+ }
1315
+ if (firstMismatch === -1)
1316
+ return matches;
1317
+ if (secondMismatch === -1)
1318
+ return null;
1319
+ let firstOldNode = curr[firstMismatch];
1320
+ let secondOldNode = curr[secondMismatch];
1321
+ let firstNextNode = next[firstMismatch];
1322
+ let secondNextNode = next[secondMismatch];
1323
+ if (firstOldNode.key !== secondNextNode.key ||
1324
+ firstOldNode.type !== secondNextNode.type ||
1325
+ secondOldNode.key !== firstNextNode.key ||
1326
+ secondOldNode.type !== firstNextNode.type) {
1327
+ return null;
1328
+ }
1329
+ matches[firstMismatch] = { oldIndex: secondMismatch };
1330
+ matches[secondMismatch] = { oldIndex: firstMismatch };
1331
+ return matches;
1332
+ }
1333
+ function analyzeKeyedChildMatches(currentLength, matches) {
1334
+ let hasRemovals = matches.length !== currentLength;
1335
+ let canSkipPlacement = true;
1336
+ let lastOldIndex = -1;
1337
+ let sawNewNode = false;
1338
+ for (let match of matches) {
1339
+ if (match.oldIndex < 0) {
1340
+ hasRemovals = true;
1341
+ sawNewNode = true;
1342
+ continue;
1343
+ }
1344
+ if (sawNewNode || match.oldIndex < lastOldIndex) {
1345
+ canSkipPlacement = false;
1346
+ }
1347
+ lastOldIndex = match.oldIndex;
1348
+ }
1349
+ return { hasRemovals, canSkipPlacement };
1350
+ }
1351
+ function lisMatches(matches) {
1352
+ let predecessors = Array.from({ length: matches.length });
1353
+ let tails = [];
1354
+ for (let index = 0; index < matches.length; index++) {
1355
+ let value = matches[index].oldIndex + 1;
1356
+ if (value === 0)
1357
+ continue;
1358
+ let low = 0;
1359
+ let high = tails.length;
1360
+ while (low < high) {
1361
+ let middle = (low + high) >> 1;
1362
+ if (matches[tails[middle]].oldIndex + 1 < value) {
1363
+ low = middle + 1;
1125
1364
  }
1126
1365
  else {
1127
- target = lastPlaced.nextSibling;
1128
- }
1129
- if (target === null && anchor)
1130
- target = anchor;
1131
- // If target lies within the range we're moving, skip the move.
1132
- if (target && domRangeContainsNode(firstDom, lastDom, target)) {
1133
- // no-op
1134
- }
1135
- else if (firstDom !== target) {
1136
- moveDomRange(domParent, firstDom, lastDom, target);
1366
+ high = middle;
1137
1367
  }
1138
1368
  }
1139
- if (lastDom)
1140
- lastPlaced = lastDom;
1141
- // Clear internal flags for next diff
1142
- childVNode._flags = 0;
1143
- childVNode._index = undefined;
1369
+ predecessors[index] = low > 0 ? tails[low - 1] : -1;
1370
+ tails[low] = index;
1144
1371
  }
1145
- return;
1372
+ let cursor = tails.at(-1) ?? -1;
1373
+ for (let index = tails.length - 1; index >= 0; index--) {
1374
+ tails[index] = cursor;
1375
+ cursor = predecessors[cursor] ?? -1;
1376
+ }
1377
+ return tails;
1378
+ }
1379
+ function placeVNode(node, domParent, anchor) {
1380
+ let firstDom = findFirstDomAnchor(node);
1381
+ if (!firstDom || firstDom.parentNode !== domParent)
1382
+ return;
1383
+ let lastDom = findLastDomAnchor(node);
1384
+ if (!lastDom)
1385
+ return;
1386
+ if (anchor && domRangeContainsNode(firstDom, lastDom, anchor))
1387
+ return;
1388
+ if (firstDom === anchor)
1389
+ return;
1390
+ moveDomRange(domParent, firstDom, lastDom, anchor);
1146
1391
  }
1147
1392
  export function findFirstDomAnchor(node) {
1148
1393
  if (!node)
@@ -1221,10 +1466,13 @@ function shouldDispatchInlineMixinLifecycle(node) {
1221
1466
  }
1222
1467
  return true;
1223
1468
  }
1224
- export function findNextSiblingDomAnchor(curr, vParent) {
1469
+ export function findNextSiblingDomAnchor(curr) {
1470
+ let vParent = curr._parent;
1225
1471
  if (!vParent || !Array.isArray(vParent._children))
1226
1472
  return null;
1227
1473
  let children = vParent._children;
1474
+ if (children.length === 0)
1475
+ return findNextSiblingDomAnchor(vParent);
1228
1476
  let idx = children.indexOf(curr);
1229
1477
  if (idx === -1)
1230
1478
  return null;
@@ -1233,6 +1481,9 @@ export function findNextSiblingDomAnchor(curr, vParent) {
1233
1481
  if (dom)
1234
1482
  return dom;
1235
1483
  }
1484
+ if (isFragmentNode(vParent)) {
1485
+ return findNextSiblingDomAnchor(vParent);
1486
+ }
1236
1487
  return null;
1237
1488
  }
1238
1489
  function reclaimPersistedMixinNode(persistedNode, newNode, frame, scheduler, styles, vParent, rootTarget) {
@@ -1242,17 +1493,21 @@ function reclaimPersistedMixinNode(persistedNode, newNode, frame, scheduler, sty
1242
1493
  newNode._parent = vParent;
1243
1494
  newNode._controller = persistedNode._controller;
1244
1495
  newNode._mixState = persistedNode._mixState;
1496
+ newNode._directEventState = persistedNode._directEventState;
1245
1497
  newNode._controlledState = persistedNode._controlledState;
1246
1498
  let prevProps = getHostProps(persistedNode);
1247
1499
  let nextProps = resolveNodeMixProps(newNode, frame, scheduler, newNode._mixState);
1248
1500
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
1249
1501
  dispatchMixinBeforeUpdate(newNode._mixState);
1250
1502
  }
1251
- diffHostProps(prevProps, nextProps, persistedNode._dom);
1503
+ patchHostProps(prevProps, nextProps, persistedNode._dom);
1504
+ syncDirectEventListeners(newNode);
1252
1505
  ensureControlledReflection(newNode, scheduler);
1253
1506
  syncControlledReflection(newNode, nextProps);
1254
1507
  diffChildren(persistedNode._children, newNode._children, persistedNode._dom, frame, scheduler, styles, newNode, rootTarget);
1255
- bindNodeMixRuntime(newNode, frame, scheduler, styles, true);
1508
+ if (newNode._mixState) {
1509
+ bindNodeMixRuntime(newNode, frame, scheduler, styles, true);
1510
+ }
1256
1511
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
1257
1512
  scheduler.enqueueCommitPhase([
1258
1513
  () => dispatchMixinCommit(newNode._mixState),