@remix-run/ui 0.1.0 → 0.1.2

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