@remix-run/ui 0.1.1 → 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 (195) 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 +41 -47
  99. package/dist/runtime/frame.js.map +1 -1
  100. package/dist/runtime/jsx.js +2 -28
  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.js +9 -1
  108. package/dist/runtime/navigation.js.map +1 -1
  109. package/dist/runtime/reconcile.js +433 -192
  110. package/dist/runtime/reconcile.js.map +1 -1
  111. package/dist/runtime/run.d.ts +17 -2
  112. package/dist/runtime/run.js.map +1 -1
  113. package/dist/runtime/stream-protocol.d.ts +8 -0
  114. package/dist/runtime/stream-protocol.js +20 -0
  115. package/dist/runtime/stream-protocol.js.map +1 -0
  116. package/dist/runtime/to-vnode.js +5 -9
  117. package/dist/runtime/to-vnode.js.map +1 -1
  118. package/dist/runtime/vnode.d.ts +5 -4
  119. package/dist/runtime/vnode.js +2 -3
  120. package/dist/runtime/vnode.js.map +1 -1
  121. package/dist/server/stream.js +76 -106
  122. package/dist/server/stream.js.map +1 -1
  123. package/dist/style/layers.d.ts +2 -0
  124. package/dist/style/layers.js +3 -0
  125. package/dist/style/layers.js.map +1 -0
  126. package/dist/style/stylesheet.d.ts +3 -1
  127. package/dist/style/stylesheet.js +69 -8
  128. package/dist/style/stylesheet.js.map +1 -1
  129. package/dist/theme/glyph-contract.d.ts +1 -1
  130. package/dist/theme/runtime.js +4 -1
  131. package/dist/theme/runtime.js.map +1 -1
  132. package/package.json +3 -3
  133. package/src/animation/README.md +314 -0
  134. package/src/animation/animate-layout-mixin.ts +79 -71
  135. package/src/components/accordion/README.md +166 -0
  136. package/src/components/accordion/accordion.tsx +8 -10
  137. package/src/components/accordion/demos/card.demo.tsx +113 -0
  138. package/src/components/accordion/demos/multiple.demo.tsx +56 -0
  139. package/src/components/accordion/demos/overview.demo.tsx +56 -0
  140. package/src/components/anchor/README.md +153 -0
  141. package/src/components/breadcrumbs/README.md +55 -0
  142. package/src/components/breadcrumbs/breadcrumbs.tsx +0 -2
  143. package/src/components/breadcrumbs/demos/basic.demo.tsx +17 -0
  144. package/src/components/breadcrumbs/demos/separator.demo.tsx +18 -0
  145. package/src/components/button/README.md +4 -4
  146. package/src/components/button/button.tsx +88 -2
  147. package/src/components/button/demos/aliases.demo.tsx +27 -0
  148. package/src/components/button/demos/basic.demo.tsx +30 -0
  149. package/src/components/button/demos/states.demo.tsx +52 -0
  150. package/src/components/combobox/combobox.tsx +8 -10
  151. package/src/components/combobox/demos/overview.demo.tsx +199 -0
  152. package/src/components/glyph/README.md +72 -0
  153. package/src/components/listbox/README.md +115 -0
  154. package/src/components/listbox/demos/overview.demo.tsx +101 -0
  155. package/src/components/listbox/listbox.ts +23 -32
  156. package/src/components/menu/README.md +96 -0
  157. package/src/components/menu/demos/bubbling.demo.tsx +34 -0
  158. package/src/components/menu/demos/overview.demo.tsx +76 -0
  159. package/src/components/menu/menu.tsx +35 -41
  160. package/src/components/popover/README.md +3 -3
  161. package/src/components/popover/demos/overview.demo.tsx +143 -0
  162. package/src/components/select/README.md +107 -0
  163. package/src/components/select/demos/deconstructed.demo.tsx +95 -0
  164. package/src/components/select/demos/overview.demo.tsx +51 -0
  165. package/src/components/select/select.tsx +28 -28
  166. package/src/components/tabs/tabs.tsx +10 -5
  167. package/src/index.ts +1 -1
  168. package/src/runtime/component.ts +94 -82
  169. package/src/runtime/core/attributes.ts +97 -0
  170. package/src/runtime/core/children.ts +42 -0
  171. package/src/runtime/core/props.ts +185 -0
  172. package/src/runtime/core/vnode.ts +48 -0
  173. package/src/runtime/create-element.ts +2 -1
  174. package/src/runtime/diff-dom.ts +59 -12
  175. package/src/runtime/diff-props.ts +2 -187
  176. package/src/runtime/dom.ts +4 -0
  177. package/src/runtime/frame.ts +71 -55
  178. package/src/runtime/jsx.ts +2 -29
  179. package/src/runtime/mixins/mixin.ts +9 -4
  180. package/src/runtime/mixins/on-mixin.ts +20 -12
  181. package/src/runtime/navigation.ts +10 -1
  182. package/src/runtime/reconcile.ts +540 -209
  183. package/src/runtime/run.ts +17 -1
  184. package/src/runtime/stream-protocol.ts +27 -0
  185. package/src/runtime/to-vnode.ts +5 -8
  186. package/src/runtime/vnode.ts +6 -9
  187. package/src/server/README.md +88 -0
  188. package/src/server/stream.ts +99 -116
  189. package/src/style/layers.ts +2 -0
  190. package/src/style/stylesheet.ts +75 -10
  191. package/src/test/README.md +107 -0
  192. package/src/test/utils.ts +3 -1
  193. package/src/theme/README.md +103 -0
  194. package/src/theme/runtime.ts +4 -1
  195. package/src/utils/scroll-lock/README.md +33 -0
@@ -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
  },
@@ -319,16 +344,20 @@ function diffHost(curr, next, frame, scheduler, styles, vParent, rootTarget) {
319
344
  curr._dom.innerHTML = '';
320
345
  }
321
346
  diffChildren(curr._children, next._children, curr._dom, frame, scheduler, styles, next, rootTarget);
322
- diffHostProps(currProps, nextProps, curr._dom);
347
+ patchHostProps(currProps, nextProps, curr._dom);
323
348
  next._dom = curr._dom;
324
349
  next._parent = vParent;
325
350
  next._controller = curr._controller;
351
+ next._directEventState = curr._directEventState;
326
352
  next._controlledState = curr._controlledState;
353
+ syncDirectEventListeners(next);
327
354
  if (next._controlledState || shouldTrackControlledReflection(nextProps)) {
328
355
  ensureControlledReflection(next, scheduler);
329
356
  syncControlledReflection(next, nextProps);
330
357
  }
331
- bindNodeMixRuntime(next, frame, scheduler, styles);
358
+ if (next._mixState) {
359
+ bindNodeMixRuntime(next, frame, scheduler, styles);
360
+ }
332
361
  if (shouldDispatchMixinLifecycle) {
333
362
  scheduler.enqueueCommitPhase([() => dispatchMixinCommit(nextMixState)]);
334
363
  }
@@ -338,11 +367,94 @@ function setupHostNode(node, dom, scheduler) {
338
367
  node._dom = dom;
339
368
  let props = getHostProps(node);
340
369
  let committedNode = node;
370
+ syncDirectEventListeners(committedNode);
341
371
  if (shouldTrackControlledReflection(props)) {
342
372
  ensureControlledReflection(committedNode, scheduler);
343
373
  syncControlledReflection(committedNode, props);
344
374
  }
345
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
+ }
346
458
  function diffText(curr, next, vParent) {
347
459
  if (curr._text !== next._text) {
348
460
  curr._dom.textContent = next._text;
@@ -414,9 +526,11 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
414
526
  }
415
527
  // Render explicit <head> children directly into document.head.
416
528
  diffChildren(null, node._children, targetHead, frame, scheduler, styles, node, rootTarget, childCursor);
417
- diffHostProps({}, hostProps, targetHead);
529
+ patchHostProps({}, hostProps, targetHead);
418
530
  setupHostNode(node, targetHead, scheduler);
419
- bindNodeMixRuntime(node, frame, scheduler, styles);
531
+ if (node._mixState) {
532
+ bindNodeMixRuntime(node, frame, scheduler, styles);
533
+ }
420
534
  return cursor;
421
535
  }
422
536
  }
@@ -432,7 +546,7 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
432
546
  let cursorTag = node._svg ? cursor.tagName : cursor.tagName.toLowerCase();
433
547
  if (cursorTag === node.type) {
434
548
  let nextCursor = cursor.nextSibling;
435
- diffHostProps({}, hostProps, cursor);
549
+ patchHostProps({}, hostProps, cursor);
436
550
  // Handle innerHTML prop
437
551
  if (hostProps.innerHTML != null) {
438
552
  cursor.innerHTML = hostProps.innerHTML;
@@ -443,7 +557,9 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
443
557
  diffChildren(null, node._children, cursor, frame, scheduler, styles, node, rootTarget, childCursor);
444
558
  }
445
559
  setupHostNode(node, cursor, scheduler);
446
- bindNodeMixRuntime(node, frame, scheduler, styles);
560
+ if (node._mixState) {
561
+ bindNodeMixRuntime(node, frame, scheduler, styles);
562
+ }
447
563
  return nextCursor;
448
564
  }
449
565
  else {
@@ -455,7 +571,7 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
455
571
  if (nextTag === node.type) {
456
572
  let nextCursor = nextSibling.nextSibling;
457
573
  // Found a match after skipping - adopt it and leave skipped node in place
458
- diffHostProps({}, hostProps, nextSibling);
574
+ patchHostProps({}, hostProps, nextSibling);
459
575
  if (hostProps.innerHTML != null) {
460
576
  nextSibling.innerHTML = hostProps.innerHTML;
461
577
  }
@@ -464,7 +580,9 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
464
580
  diffChildren(null, node._children, nextSibling, frame, scheduler, styles, node, rootTarget, childCursor);
465
581
  }
466
582
  setupHostNode(node, nextSibling, scheduler);
467
- bindNodeMixRuntime(node, frame, scheduler, styles);
583
+ if (node._mixState) {
584
+ bindNodeMixRuntime(node, frame, scheduler, styles);
585
+ }
468
586
  return nextCursor;
469
587
  }
470
588
  }
@@ -476,7 +594,7 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
476
594
  let dom = node._svg
477
595
  ? document.createElementNS(SVG_NS, node.type)
478
596
  : document.createElement(node.type);
479
- diffHostProps({}, hostProps, dom);
597
+ patchHostProps({}, hostProps, dom);
480
598
  // Handle innerHTML prop
481
599
  if (hostProps.innerHTML != null) {
482
600
  dom.innerHTML = hostProps.innerHTML;
@@ -485,7 +603,9 @@ function insert(node, domParent, frame, scheduler, styles, vParent, rootTarget,
485
603
  diffChildren(null, node._children, dom, frame, scheduler, styles, node, rootTarget);
486
604
  }
487
605
  setupHostNode(node, dom, scheduler);
488
- bindNodeMixRuntime(node, frame, scheduler, styles, false, domParent);
606
+ if (node._mixState) {
607
+ bindNodeMixRuntime(node, frame, scheduler, styles, false, domParent);
608
+ }
489
609
  doInsert(dom);
490
610
  return cursor;
491
611
  }
@@ -835,6 +955,7 @@ function cleanupDescendants(node, scheduler, styles) {
835
955
  cleanupDescendants(child, scheduler, styles);
836
956
  }
837
957
  teardownMixins(node._mixState);
958
+ teardownDirectEventListeners(node);
838
959
  teardownControlledReflection(node);
839
960
  if (node._controller)
840
961
  node._controller.abort();
@@ -916,6 +1037,7 @@ function performHostNodeRemoval(node, domParent, scheduler, styles) {
916
1037
  }
917
1038
  }
918
1039
  teardownMixins(node._mixState);
1040
+ teardownDirectEventListeners(node);
919
1041
  teardownControlledReflection(node);
920
1042
  // Never remove the real document.head node when reconciling a <head> vnode.
921
1043
  if (!isHeadHostNode(node)) {
@@ -925,55 +1047,35 @@ function performHostNodeRemoval(node, domParent, scheduler, styles) {
925
1047
  node._controller.abort();
926
1048
  }
927
1049
  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.
1050
+ let hasKeys = hasKeyedChildren(next);
959
1051
  if (curr === null) {
1052
+ if (hasKeys) {
1053
+ warnDuplicateKeys(next);
1054
+ }
960
1055
  for (let node of next) {
961
1056
  cursor = insert(node, domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
962
1057
  }
963
1058
  vParent._children = next;
964
1059
  return cursor;
965
1060
  }
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.
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
+ }
970
1072
  if (!hasKeys) {
971
- for (let i = 0; i < nextLength; i++) {
972
- 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;
973
1075
  diffVNodes(currentNode, next[i], domParent, frame, scheduler, styles, vParent, rootTarget, anchor, cursor);
974
1076
  }
975
- if (currLength > nextLength) {
976
- for (let i = nextLength; i < currLength; i++) {
1077
+ if (curr.length > next.length) {
1078
+ for (let i = next.length; i < curr.length; i++) {
977
1079
  let node = curr[i];
978
1080
  if (node)
979
1081
  remove(node, domParent, scheduler, styles);
@@ -982,167 +1084,302 @@ function diffChildren(curr, next, domParent, frame, scheduler, styles, vParent,
982
1084
  vParent._children = next;
983
1085
  return;
984
1086
  }
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
- }
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;
1002
1097
  }
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
- }
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;
1016
1109
  }
1110
+ return true;
1111
+ }
1112
+ if (isFragmentNode(node)) {
1113
+ return canBulkClearChildren(node._children);
1017
1114
  }
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;
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]);
1025
1135
  continue;
1026
1136
  }
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
- }
1137
+ if (seenKeys.has(node.key)) {
1138
+ duplicateKeys ??= new Set();
1139
+ duplicateKeys.add(node.key);
1043
1140
  }
1044
1141
  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;
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;
1051
1164
  }
1052
1165
  }
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;
1166
+ for (let oldIndex = 0; oldIndex < curr.length; oldIndex++) {
1167
+ if (usedOldIndexes[oldIndex] === 0) {
1168
+ remove(curr[oldIndex], domParent, scheduler, styles);
1060
1169
  }
1061
1170
  }
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++;
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;
1073
1213
  }
1074
1214
  }
1075
- childVNode._flags = (childVNode._flags ?? 0) | INSERT_VNODE;
1076
1215
  }
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;
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;
1090
1225
  }
1091
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 });
1092
1244
  }
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
- }
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;
1100
1275
  }
1276
+ matches.push({ oldIndex });
1277
+ oldIndex++;
1101
1278
  }
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)
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 });
1109
1294
  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
- }
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;
1125
1356
  }
1126
1357
  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);
1358
+ high = middle;
1137
1359
  }
1138
1360
  }
1139
- if (lastDom)
1140
- lastPlaced = lastDom;
1141
- // Clear internal flags for next diff
1142
- childVNode._flags = 0;
1143
- childVNode._index = undefined;
1361
+ predecessors[index] = low > 0 ? tails[low - 1] : -1;
1362
+ tails[low] = index;
1144
1363
  }
1145
- 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);
1146
1383
  }
1147
1384
  export function findFirstDomAnchor(node) {
1148
1385
  if (!node)
@@ -1242,17 +1479,21 @@ function reclaimPersistedMixinNode(persistedNode, newNode, frame, scheduler, sty
1242
1479
  newNode._parent = vParent;
1243
1480
  newNode._controller = persistedNode._controller;
1244
1481
  newNode._mixState = persistedNode._mixState;
1482
+ newNode._directEventState = persistedNode._directEventState;
1245
1483
  newNode._controlledState = persistedNode._controlledState;
1246
1484
  let prevProps = getHostProps(persistedNode);
1247
1485
  let nextProps = resolveNodeMixProps(newNode, frame, scheduler, newNode._mixState);
1248
1486
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
1249
1487
  dispatchMixinBeforeUpdate(newNode._mixState);
1250
1488
  }
1251
- diffHostProps(prevProps, nextProps, persistedNode._dom);
1489
+ patchHostProps(prevProps, nextProps, persistedNode._dom);
1490
+ syncDirectEventListeners(newNode);
1252
1491
  ensureControlledReflection(newNode, scheduler);
1253
1492
  syncControlledReflection(newNode, nextProps);
1254
1493
  diffChildren(persistedNode._children, newNode._children, persistedNode._dom, frame, scheduler, styles, newNode, rootTarget);
1255
- bindNodeMixRuntime(newNode, frame, scheduler, styles, true);
1494
+ if (newNode._mixState) {
1495
+ bindNodeMixRuntime(newNode, frame, scheduler, styles, true);
1496
+ }
1256
1497
  if (shouldDispatchInlineMixinLifecycle(persistedNode._dom)) {
1257
1498
  scheduler.enqueueCommitPhase([
1258
1499
  () => dispatchMixinCommit(newNode._mixState),