@remix-run/ui 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +74 -1
  2. package/dist/accordion/index.js +2 -2
  3. package/dist/accordion/primitives.d.ts +1 -1
  4. package/dist/accordion/primitives.js.map +1 -1
  5. package/dist/anchor/index.js.map +1 -1
  6. package/dist/animation/animate-layout-mixin.js +1 -1
  7. package/dist/animation/animate-layout-mixin.js.map +1 -1
  8. package/dist/animation/animate-mixins.js +2 -2
  9. package/dist/animation/animate-mixins.js.map +1 -1
  10. package/dist/animation/demos/color-interpolation.js +1 -1
  11. package/dist/animation/demos/color-interpolation.js.map +1 -1
  12. package/dist/animation/demos/drag-release.d.ts +1 -1
  13. package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
  14. package/dist/animation/demos/material-ripple.js +1 -3
  15. package/dist/animation/demos/material-ripple.js.map +1 -1
  16. package/dist/animation/index.js +4 -4
  17. package/dist/animation/spring.js.map +1 -1
  18. package/dist/breadcrumbs/index.js +2 -2
  19. package/dist/button/index.js +25 -25
  20. package/dist/button/index.js.map +1 -1
  21. package/dist/checkbox/index.js +11 -9
  22. package/dist/checkbox/index.js.map +1 -1
  23. package/dist/combobox/index.js +4 -4
  24. package/dist/combobox/index.js.map +1 -1
  25. package/dist/combobox/primitives.d.ts +1 -1
  26. package/dist/combobox/primitives.js +5 -5
  27. package/dist/combobox/primitives.js.map +1 -1
  28. package/dist/dev/refresh.d.ts +1 -0
  29. package/dist/dev/refresh.js +2 -0
  30. package/dist/dev/refresh.js.map +1 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +14 -14
  33. package/dist/input/index.js +9 -9
  34. package/dist/input/index.js.map +1 -1
  35. package/dist/jsx-runtime.js +2 -2
  36. package/dist/listbox/index.js +2 -2
  37. package/dist/menu/hover-aim.js.map +1 -1
  38. package/dist/menu/index.js +3 -3
  39. package/dist/menu/primitives.d.ts +1 -1
  40. package/dist/menu/primitives.js +6 -6
  41. package/dist/menu/primitives.js.map +1 -1
  42. package/dist/popover/index.js +3 -3
  43. package/dist/popover/scroll-lock.js.map +1 -1
  44. package/dist/radio/index.js +7 -7
  45. package/dist/radio/index.js.map +1 -1
  46. package/dist/runtime/client-entry-boundary.d.ts +14 -0
  47. package/dist/runtime/client-entry-boundary.js +29 -0
  48. package/dist/runtime/client-entry-boundary.js.map +1 -0
  49. package/dist/runtime/component.d.ts +19 -4
  50. package/dist/runtime/component.js +39 -9
  51. package/dist/runtime/component.js.map +1 -1
  52. package/dist/runtime/core/attributes.js +36 -3
  53. package/dist/runtime/core/attributes.js.map +1 -1
  54. package/dist/runtime/core/children.d.ts +2 -0
  55. package/dist/runtime/core/children.js +26 -2
  56. package/dist/runtime/core/children.js.map +1 -1
  57. package/dist/runtime/core/props.js +2 -2
  58. package/dist/runtime/core/vnode.d.ts +3 -3
  59. package/dist/runtime/core/vnode.js +22 -1
  60. package/dist/runtime/core/vnode.js.map +1 -1
  61. package/dist/runtime/create-element.d.ts +2 -2
  62. package/dist/runtime/create-element.js +3 -2
  63. package/dist/runtime/create-element.js.map +1 -1
  64. package/dist/runtime/diff-dom.js +307 -220
  65. package/dist/runtime/diff-dom.js.map +1 -1
  66. package/dist/runtime/diff-props.js +3 -3
  67. package/dist/runtime/dom.d.ts +12 -0
  68. package/dist/runtime/element-function.d.ts +3 -0
  69. package/dist/runtime/element-function.js +2 -0
  70. package/dist/runtime/element-function.js.map +1 -0
  71. package/dist/runtime/form-navigation.d.ts +25 -0
  72. package/dist/runtime/form-navigation.js +132 -0
  73. package/dist/runtime/form-navigation.js.map +1 -0
  74. package/dist/runtime/frame-resolution.d.ts +5 -0
  75. package/dist/runtime/frame-resolution.js +10 -0
  76. package/dist/runtime/frame-resolution.js.map +1 -0
  77. package/dist/runtime/frame.d.ts +59 -15
  78. package/dist/runtime/frame.js +383 -114
  79. package/dist/runtime/frame.js.map +1 -1
  80. package/dist/runtime/jsx.d.ts +1 -1
  81. package/dist/runtime/jsx.js +1 -1
  82. package/dist/runtime/jsx.js.map +1 -1
  83. package/dist/runtime/key.d.ts +1 -0
  84. package/dist/runtime/key.js +2 -0
  85. package/dist/runtime/key.js.map +1 -0
  86. package/dist/runtime/mixins/attrs-mixin.js +2 -2
  87. package/dist/runtime/mixins/link-mixin.js +4 -3
  88. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  89. package/dist/runtime/mixins/mixin.d.ts +15 -10
  90. package/dist/runtime/mixins/mixin.js +4 -4
  91. package/dist/runtime/mixins/mixin.js.map +1 -1
  92. package/dist/runtime/mixins/on-mixin.js +1 -1
  93. package/dist/runtime/mixins/ref-mixin.js +1 -1
  94. package/dist/runtime/module-preloader.d.ts +8 -0
  95. package/dist/runtime/module-preloader.js +95 -0
  96. package/dist/runtime/module-preloader.js.map +1 -0
  97. package/dist/runtime/navigation.d.ts +4 -1
  98. package/dist/runtime/navigation.js +157 -32
  99. package/dist/runtime/navigation.js.map +1 -1
  100. package/dist/runtime/reconcile.d.ts +11 -10
  101. package/dist/runtime/reconcile.js +549 -421
  102. package/dist/runtime/reconcile.js.map +1 -1
  103. package/dist/runtime/refresh.d.ts +28 -0
  104. package/dist/runtime/refresh.js +31 -0
  105. package/dist/runtime/refresh.js.map +1 -0
  106. package/dist/runtime/render.js +1 -1
  107. package/dist/runtime/render.js.map +1 -1
  108. package/dist/runtime/run.d.ts +3 -1
  109. package/dist/runtime/run.js +15 -7
  110. package/dist/runtime/run.js.map +1 -1
  111. package/dist/runtime/scheduler.js +8 -16
  112. package/dist/runtime/scheduler.js.map +1 -1
  113. package/dist/runtime/to-vnode.d.ts +2 -2
  114. package/dist/runtime/to-vnode.js +68 -20
  115. package/dist/runtime/to-vnode.js.map +1 -1
  116. package/dist/runtime/vdom.d.ts +4 -2
  117. package/dist/runtime/vdom.js +87 -42
  118. package/dist/runtime/vdom.js.map +1 -1
  119. package/dist/runtime/vnode.d.ts +138 -67
  120. package/dist/runtime/vnode.js +17 -12
  121. package/dist/runtime/vnode.js.map +1 -1
  122. package/dist/select/index.js +11 -11
  123. package/dist/select/index.js.map +1 -1
  124. package/dist/select/primitives.d.ts +1 -1
  125. package/dist/select/primitives.js +6 -6
  126. package/dist/server/stream.d.ts +2 -0
  127. package/dist/server/stream.js +74 -29
  128. package/dist/server/stream.js.map +1 -1
  129. package/dist/shared/focus-styles.d.ts +1 -1
  130. package/dist/shared/focus-styles.js +1 -1
  131. package/dist/shared/focus-styles.js.map +1 -1
  132. package/dist/shared/listbox-popover-styles.js +1 -1
  133. package/dist/shared/style-values.js +26 -26
  134. package/dist/shared/style-values.js.map +1 -1
  135. package/dist/style/css-mixin.js +3 -3
  136. package/dist/style/index.js +2 -2
  137. package/dist/style/style.js +25 -6
  138. package/dist/style/style.js.map +1 -1
  139. package/dist/style/stylesheet.d.ts +0 -1
  140. package/dist/style/stylesheet.js +13 -54
  141. package/dist/style/stylesheet.js.map +1 -1
  142. package/dist/tabs/index.js +11 -11
  143. package/dist/tabs/index.js.map +1 -1
  144. package/dist/tabs/primitives.d.ts +1 -1
  145. package/dist/test.js +1 -1
  146. package/dist/toggle/index.js +9 -9
  147. package/dist/toggle/index.js.map +1 -1
  148. package/dist/toggle/primitives.d.ts +1 -1
  149. package/package.json +10 -7
  150. package/src/animation/animate-mixins.ts +3 -3
  151. package/src/animation/demos/color-interpolation.tsx +1 -1
  152. package/src/button/index.ts +24 -24
  153. package/src/checkbox/index.ts +10 -8
  154. package/src/combobox/index.tsx +1 -1
  155. package/src/dev/refresh.ts +1 -0
  156. package/src/index.ts +1 -1
  157. package/src/input/index.ts +8 -8
  158. package/src/radio/index.ts +6 -5
  159. package/src/runtime/client-entry-boundary.ts +54 -0
  160. package/src/runtime/component.ts +65 -16
  161. package/src/runtime/core/attributes.ts +35 -1
  162. package/src/runtime/core/children.ts +24 -2
  163. package/src/runtime/core/vnode.ts +25 -4
  164. package/src/runtime/create-element.ts +6 -5
  165. package/src/runtime/demos/readme.demo.tsx +4 -4
  166. package/src/runtime/diff-dom.ts +370 -244
  167. package/src/runtime/dom.ts +331 -214
  168. package/src/runtime/element-function.ts +1 -0
  169. package/src/runtime/event-listeners.ts +1 -1
  170. package/src/runtime/form-navigation.ts +184 -0
  171. package/src/runtime/frame-resolution.ts +13 -0
  172. package/src/runtime/frame.ts +500 -124
  173. package/src/runtime/jsx.ts +3 -5
  174. package/src/runtime/key.ts +1 -0
  175. package/src/runtime/mixins/link-mixin.ts +1 -0
  176. package/src/runtime/mixins/mixin.ts +21 -18
  177. package/src/runtime/module-preloader.ts +115 -0
  178. package/src/runtime/navigation.ts +204 -30
  179. package/src/runtime/reconcile.ts +762 -759
  180. package/src/runtime/refresh.ts +46 -0
  181. package/src/runtime/run.ts +12 -2
  182. package/src/runtime/scheduler.ts +9 -29
  183. package/src/runtime/to-vnode.ts +85 -23
  184. package/src/runtime/vdom.ts +89 -70
  185. package/src/runtime/vnode.ts +192 -88
  186. package/src/select/index.tsx +9 -9
  187. package/src/server/README.md +13 -0
  188. package/src/server/stream.ts +93 -30
  189. package/src/shared/focus-styles.ts +1 -1
  190. package/src/shared/style-values.ts +26 -26
  191. package/src/style/style.ts +25 -6
  192. package/src/style/stylesheet.ts +32 -57
  193. package/src/tabs/index.tsx +12 -11
  194. package/src/test/README.md +151 -60
  195. package/src/toggle/index.tsx +9 -7
@@ -1,4 +1,6 @@
1
- import { invariant } from "./invariant.js";
1
+ import { invariant } from './invariant.js';
2
+ import { disposeClientEntryBoundary, getClientEntryBoundaryOwner } from './client-entry-boundary.js';
3
+ const REMIX_PRESERVE_DOM_ATTRIBUTE = 'rmx-preserve-dom';
2
4
  export function diffNodes(curr, next, context) {
3
5
  let parent = curr[0]?.parentNode ?? context.regionParent ?? null;
4
6
  invariant(parent, 'Parent node not found');
@@ -6,60 +8,7 @@ export function diffNodes(curr, next, context) {
6
8
  // nodes before the region tail ref rather than appending to the parent.
7
9
  let regionTailRef = context.regionTailRef ??
8
10
  (curr.length > 0 ? curr[curr.length - 1].nextSibling : null);
9
- let currentIndex = 0;
10
- let nextIndex = 0;
11
- while (currentIndex < curr.length || nextIndex < next.length) {
12
- let c = curr[currentIndex];
13
- let n = next[nextIndex];
14
- if (!c && n) {
15
- if (regionTailRef) {
16
- parent.insertBefore(n, regionTailRef);
17
- }
18
- else {
19
- parent.appendChild(n);
20
- }
21
- nextIndex++;
22
- }
23
- else if (c && !n) {
24
- removeNode(c, parent, context);
25
- currentIndex++;
26
- }
27
- else if (c && n) {
28
- let replacement = getCommentMarkerRangeReplacement(c, n, curr, next, currentIndex, nextIndex, context);
29
- if (replacement) {
30
- replaceCommentMarkerRange(replacement, parent, context);
31
- currentIndex = replacement.currentEndIndex + 1;
32
- nextIndex = replacement.nextEndIndex + 1;
33
- continue;
34
- }
35
- // Skip hydrated client-entry marker ranges; hydration pass re-renders
36
- // roots with new props from incoming payload
37
- if (isVirtualRootStartMarker(c) && isVirtualRootStartMarker(n)) {
38
- let currentEnd = findHydrationEndMarker(c);
39
- let nextEnd = findHydrationEndMarker(n);
40
- let nextData = n.data;
41
- if (c.data !== nextData)
42
- c.data = nextData;
43
- let currentEndIndex = curr.indexOf(currentEnd);
44
- let nextEndIndex = next.indexOf(nextEnd);
45
- currentIndex = currentEndIndex + 1;
46
- nextIndex = nextEndIndex + 1;
47
- continue;
48
- }
49
- let cursor = diffNode(c, n, context);
50
- if (cursor) {
51
- let nextEndIndex = next.indexOf(cursor);
52
- let currentEndIndex = isFrameStartMarker(c) && isFrameEndMarker(cursor)
53
- ? findFrameEndIndex(curr, currentIndex)
54
- : currentIndex;
55
- currentIndex = currentEndIndex + 1;
56
- nextIndex = nextEndIndex + 1;
57
- continue;
58
- }
59
- currentIndex++;
60
- nextIndex++;
61
- }
62
- }
11
+ diffSiblingUnits(curr, next, parent, regionTailRef, context);
63
12
  }
64
13
  function diffNode(current, next, context) {
65
14
  // Text -> Text
@@ -80,40 +29,41 @@ function diffNode(current, next, context) {
80
29
  return end;
81
30
  }
82
31
  // Comment -> Comment
83
- if (isCommentNode(current) && isCommentNode(next)) {
32
+ if (isCommentNode(current) && isCommentNode(next) && markerKindsMatch(current, next)) {
84
33
  let newData = next.data;
85
- if (current.data !== newData) {
86
- let updated = false;
87
- if (isFrameStartMarker(current)) {
88
- if (shouldPreserveFrameStartMarker(current, next, context)) {
34
+ let updated = false;
35
+ if (isFrameStartMarker(current)) {
36
+ if (shouldPreserveFrameStartMarker(current, next, context)) {
37
+ if (current.data !== newData) {
89
38
  current.data = newData;
90
- updated = true;
91
- let frame = context.frameInstances.get(current);
92
- let nextMarkerData = getFrameMarkerData(next, context);
93
- if (frame && nextMarkerData) {
94
- if (nextMarkerData.status === 'resolved') {
95
- let nextEnd = findFrameEndMarker(next);
96
- let nextContent = collectFrameContentFragment(current.ownerDocument, next, nextEnd);
97
- void frame.renderMarkerContent({ ...nextMarkerData, id: getFrameId(next) }, nextContent, {
98
- signal: context.signal,
99
- });
100
- return nextEnd;
101
- }
102
- if (frame.isDisplayingResolvedContent()) {
103
- return findFrameEndMarker(next);
104
- }
105
- }
106
39
  }
107
- else {
108
- disposeFrameStartMarker(current, context);
109
- current.data = newData;
110
- updated = true;
40
+ updated = true;
41
+ let frame = context.frameInstances.get(current);
42
+ let nextMarkerData = getFrameMarkerData(next, context);
43
+ if (frame && nextMarkerData) {
44
+ if (nextMarkerData.status === 'resolved') {
45
+ let nextEnd = findFrameEndMarker(next);
46
+ let nextContent = collectFrameContentFragment(current.ownerDocument, next, nextEnd);
47
+ void frame.renderMarkerContent({ ...nextMarkerData, id: getFrameId(next) }, nextContent, {
48
+ data: context.data,
49
+ signal: context.signal,
50
+ });
51
+ return nextEnd;
52
+ }
53
+ if (frame.isDisplayingResolvedContent()) {
54
+ return findFrameEndMarker(next);
55
+ }
111
56
  }
112
57
  }
113
- if (!updated) {
58
+ else if (current.data !== newData) {
59
+ disposeFrameStartMarker(current, context);
114
60
  current.data = newData;
61
+ updated = true;
115
62
  }
116
63
  }
64
+ if (!updated && current.data !== newData) {
65
+ current.data = newData;
66
+ }
117
67
  return;
118
68
  }
119
69
  // Element -> Element
@@ -128,6 +78,8 @@ function diffNode(current, next, context) {
128
78
  return;
129
79
  }
130
80
  // Same tag: update attributes then children
81
+ if (shouldPreserveDomElement(current, next))
82
+ return;
131
83
  diffElementAttributes(current, next);
132
84
  if (shouldPreserveElementChildren(current, next))
133
85
  return;
@@ -164,6 +116,14 @@ function diffElementAttributes(current, next) {
164
116
  }
165
117
  }
166
118
  }
119
+ function shouldPreserveDomElement(current, next) {
120
+ if (!next.hasAttribute(REMIX_PRESERVE_DOM_ATTRIBUTE))
121
+ return false;
122
+ if (!current.hasAttribute(REMIX_PRESERVE_DOM_ATTRIBUTE)) {
123
+ current.setAttribute(REMIX_PRESERVE_DOM_ATTRIBUTE, '');
124
+ }
125
+ return true;
126
+ }
167
127
  function shouldPreserveLiveAttribute(current, next, name) {
168
128
  if (name === 'open') {
169
129
  if (current instanceof HTMLDetailsElement && next instanceof HTMLDetailsElement) {
@@ -219,164 +179,253 @@ function isPopoverOpen(element) {
219
179
  }
220
180
  }
221
181
  function diffElementChildren(current, next, context) {
222
- let currentChildren = Array.from(current.childNodes);
223
- let nextChildren = Array.from(next.childNodes);
224
- // Keyed map by data-key for current children
225
- let keyToIndex = new Map();
226
- for (let i = 0; i < currentChildren.length; i++) {
227
- let node = currentChildren[i];
228
- if (isElement(node)) {
229
- let key = node.getAttribute('data-key');
230
- if (key != null)
231
- keyToIndex.set(key, i);
182
+ let currentChildren;
183
+ // Allow actively managed preload link tags in the head to stay in the document
184
+ // during diffing rather than removing them which aborts the preload in Safari
185
+ if (context.isActiveModulePreload && current === current.ownerDocument.head) {
186
+ currentChildren = [];
187
+ for (let node of current.childNodes) {
188
+ if (!context.isActiveModulePreload(node))
189
+ currentChildren.push(node);
232
190
  }
233
191
  }
234
- let used = new Array(currentChildren.length).fill(false);
235
- let matchIndexForNext = new Array(nextChildren.length).fill(-1);
236
- for (let i = 0; i < nextChildren.length; i++) {
237
- let nextChild = nextChildren[i];
192
+ else {
193
+ currentChildren = Array.from(current.childNodes);
194
+ }
195
+ let nextChildren = Array.from(next.childNodes);
196
+ diffSiblingUnits(currentChildren, nextChildren, current, null, context);
197
+ }
198
+ function diffSiblingUnits(currentNodes, nextNodes, parent, regionTailRef, context) {
199
+ let currentUnits = parseSiblingUnits(currentNodes);
200
+ let nextUnits = parseSiblingUnits(nextNodes);
201
+ let keyToIndex = new Map();
202
+ for (let i = 0; i < currentUnits.length; i++) {
203
+ let key = getSiblingUnitKey(currentUnits[i]);
204
+ if (key !== undefined)
205
+ keyToIndex.set(key, i);
206
+ }
207
+ let used = new Array(currentUnits.length).fill(false);
208
+ let matchIndexForNext = new Array(nextUnits.length).fill(-1);
209
+ // Reserve globally matched keyed elements and semantic boundaries before
210
+ // positionally pairing the remaining ordinary siblings.
211
+ for (let i = 0; i < nextUnits.length; i++) {
212
+ let nextUnit = nextUnits[i];
238
213
  let matchIndex = -1;
239
- if (isFrameEndMarker(nextChild)) {
240
- for (let j = 0; j < currentChildren.length; j++) {
241
- if (!used[j] && isFrameEndMarker(currentChildren[j])) {
214
+ let key = getSiblingUnitKey(nextUnit);
215
+ if (key !== undefined) {
216
+ let keyedIndex = keyToIndex.get(key);
217
+ if (keyedIndex !== undefined &&
218
+ !used[keyedIndex] &&
219
+ siblingUnitsComparable(currentUnits[keyedIndex], nextUnit)) {
220
+ matchIndex = keyedIndex;
221
+ }
222
+ }
223
+ // Boundary identities behave like implicit keys. Repeated boundaries with
224
+ // the same identity are paired in source order because server markers do
225
+ // not carry a separate application key.
226
+ if (matchIndex === -1 && nextUnit.kind !== 'node') {
227
+ for (let j = 0; j < currentUnits.length; j++) {
228
+ let currentUnit = currentUnits[j];
229
+ if (used[j] || currentUnit.kind !== nextUnit.kind)
230
+ continue;
231
+ if (shouldPreserveBoundaryUnit(currentUnit, nextUnit, context)) {
242
232
  matchIndex = j;
243
233
  break;
244
234
  }
245
235
  }
246
236
  }
247
- else if (isElement(nextChild)) {
248
- let key = nextChild.getAttribute('data-key');
249
- if (key != null && keyToIndex.has(key)) {
250
- let idx = keyToIndex.get(key);
251
- if (!used[idx])
252
- matchIndex = idx;
253
- }
254
- }
255
- if (matchIndex === -1) {
256
- let candidateIndex = i;
257
- if (candidateIndex < currentChildren.length &&
258
- !used[candidateIndex] &&
259
- nodeTypesComparable(currentChildren[candidateIndex], nextChild)) {
260
- matchIndex = candidateIndex;
261
- }
262
- }
263
237
  if (matchIndex !== -1)
264
238
  used[matchIndex] = true;
265
239
  matchIndexForNext[i] = matchIndex;
266
240
  }
267
- // Forward pass: update matched, collect committed
268
- let committed = new Array(nextChildren.length);
269
- for (let i = 0; i < nextChildren.length; i++) {
270
- let mi = matchIndexForNext[i];
271
- if (mi !== -1) {
272
- let curChild = currentChildren[mi];
273
- let nextChild = nextChildren[i];
274
- let replacement = getCommentMarkerRangeReplacement(curChild, nextChild, currentChildren, nextChildren, mi, i, context);
275
- if (replacement) {
276
- replaceCommentMarkerRange(replacement, current, context);
277
- for (let k = mi; k <= replacement.currentEndIndex; k++)
278
- used[k] = true;
279
- committed[i] = replacement.nextStart;
280
- committed[replacement.nextEndIndex] = nextChildren[replacement.nextEndIndex];
281
- for (let j = i + 1; j < replacement.nextEndIndex; j++)
282
- committed[j] = undefined;
283
- i = replacement.nextEndIndex;
284
- continue;
285
- }
286
- let cursor = diffNode(curChild, nextChild, context);
287
- if (cursor) {
288
- // `diffNode` can preserve hydration and frame marker ranges by returning
289
- // the next end marker, so skip the owned interior nodes here.
290
- let nextEndIdx = nextChildren.indexOf(cursor);
291
- let currEndIdx = isFrameStartMarker(curChild) && isFrameEndMarker(cursor)
292
- ? findFrameEndIndex(currentChildren, mi)
293
- : findHydrationEndIndex(currentChildren, mi);
294
- // Adjacent marker ranges can pre-match into the next range and leave an
295
- // orphaned end marker behind. Clear those stale matches first.
296
- for (let j = i + 1; j <= nextEndIdx; j++) {
297
- let matchedIndex = matchIndexForNext[j];
298
- if (matchedIndex > currEndIdx) {
299
- used[matchedIndex] = false;
300
- }
301
- matchIndexForNext[j] = -1;
302
- }
303
- // Mark the entire current region as used to avoid removals.
304
- for (let k = mi; k <= currEndIdx; k++)
305
- used[k] = true;
306
- // Preserve both comment markers in committed; skip interior in reorder pass.
307
- committed[i] = curChild; // start marker
308
- committed[nextEndIdx] = currentChildren[currEndIdx]; // end marker
309
- for (let j = i + 1; j < nextEndIdx; j++)
310
- committed[j] = undefined;
311
- // Jump to end of region.
312
- i = nextEndIdx;
313
- continue;
314
- }
315
- committed[i] = curChild;
316
- }
317
- else {
318
- committed[i] = nextChildren[i];
241
+ let remainingCurrentIndexes = [];
242
+ for (let i = 0; i < currentUnits.length; i++) {
243
+ if (!used[i])
244
+ remainingCurrentIndexes.push(i);
245
+ }
246
+ let remainingNextIndexes = [];
247
+ for (let i = 0; i < nextUnits.length; i++) {
248
+ if (matchIndexForNext[i] === -1)
249
+ remainingNextIndexes.push(i);
250
+ }
251
+ let remainingLength = Math.min(remainingCurrentIndexes.length, remainingNextIndexes.length);
252
+ for (let i = 0; i < remainingLength; i++) {
253
+ let currentIndex = remainingCurrentIndexes[i];
254
+ let nextIndex = remainingNextIndexes[i];
255
+ let currentUnit = currentUnits[currentIndex];
256
+ let nextUnit = nextUnits[nextIndex];
257
+ if (currentUnit.kind !== 'node' ||
258
+ nextUnit.kind !== 'node' ||
259
+ getSiblingUnitKey(currentUnit) !== undefined ||
260
+ getSiblingUnitKey(nextUnit) !== undefined ||
261
+ !siblingUnitsComparable(currentUnit, nextUnit)) {
262
+ continue;
319
263
  }
264
+ used[currentIndex] = true;
265
+ matchIndexForNext[nextIndex] = currentIndex;
320
266
  }
321
- // Backward pass: reorder via inserts while avoiding redundant moves
322
- let anchor = undefined;
323
- for (let i = committed.length - 1; i >= 0; i--) {
324
- let node = committed[i];
325
- if (!node)
267
+ let committed = new Array(nextUnits.length);
268
+ for (let i = 0; i < nextUnits.length; i++) {
269
+ let matchIndex = matchIndexForNext[i];
270
+ let nextUnit = nextUnits[i];
271
+ if (matchIndex === -1) {
272
+ committed[i] = nextUnit;
326
273
  continue;
327
- // Use only an anchor that is actually a child of the current parent
328
- let ref = anchor && anchor.parentNode === current ? anchor : null;
329
- // Existing comment markers delimit live ranges, so do not reorder them
330
- // independently from their contents. New markers still need to be inserted
331
- // before they can be used as anchors.
332
- if (isVirtualRootStartMarker(node) ||
333
- isVirtualRootEndMarker(node) ||
334
- isFrameStartMarker(node) ||
335
- isFrameEndMarker(node)) {
336
- if (node.parentNode !== current) {
337
- current.insertBefore(node, ref);
338
- }
339
- anchor = node;
274
+ }
275
+ let currentUnit = currentUnits[matchIndex];
276
+ if (currentUnit.kind === 'node' && nextUnit.kind === 'node') {
277
+ diffNode(currentUnit.node, nextUnit.node, context);
278
+ committed[i] = currentUnit;
340
279
  continue;
341
280
  }
342
- if (node.parentNode === current) {
343
- // Node already in parent: move only if its nextSibling is not the desired ref.
344
- let targetNext = ref;
345
- let alreadyInPlace = (targetNext === null && node.nextSibling === null) || node.nextSibling === targetNext;
346
- if (!alreadyInPlace) {
347
- current.insertBefore(node, targetNext);
348
- }
281
+ invariant(currentUnit.kind !== 'node' && nextUnit.kind !== 'node', 'Expected boundaries');
282
+ let replacement = getCommentMarkerRangeReplacement(currentUnit.start, nextUnit.start, currentNodes, nextNodes, currentUnit.startIndex, nextUnit.startIndex, context);
283
+ if (replacement) {
284
+ replaceCommentMarkerRange(replacement, parent, context);
285
+ committed[i] = nextUnit;
286
+ continue;
287
+ }
288
+ let cursor = diffNode(currentUnit.start, nextUnit.start, context);
289
+ if (!cursor && currentUnit.kind === 'frame' && nextUnit.kind === 'frame') {
290
+ diffNodes(collectNodesBetween(currentUnit.start, currentUnit.end), nextNodes.slice(nextUnit.startIndex + 1, nextUnit.endIndex), {
291
+ ...context,
292
+ regionParent: parent,
293
+ regionTailRef: currentUnit.end,
294
+ });
349
295
  }
350
- else {
351
- // New node: insert relative to a valid ref or append
352
- current.insertBefore(node, ref);
296
+ committed[i] = currentUnit;
297
+ }
298
+ let anchor = regionTailRef;
299
+ for (let i = committed.length - 1; i >= 0; i--) {
300
+ let unit = committed[i];
301
+ let first = getSiblingUnitFirstNode(unit);
302
+ let ref = anchor?.parentNode === parent ? anchor : null;
303
+ if (unit.kind === 'node' &&
304
+ isPreservedDomElement(unit.node) &&
305
+ unit.node.parentNode === parent) {
306
+ anchor = unit.node;
307
+ continue;
353
308
  }
354
- // Advance anchor only after the node is placed in the correct parent
355
- if (node.parentNode === current) {
356
- anchor = node;
309
+ placeSiblingUnitBefore(unit, parent, ref);
310
+ if (first.parentNode === parent)
311
+ anchor = first;
312
+ }
313
+ for (let i = 0; i < currentUnits.length; i++) {
314
+ if (!used[i])
315
+ removeSiblingUnit(currentUnits[i], parent, context);
316
+ }
317
+ }
318
+ function parseSiblingUnits(nodes) {
319
+ let units = [];
320
+ for (let i = 0; i < nodes.length; i++) {
321
+ let node = nodes[i];
322
+ if (isVirtualRootStartMarker(node)) {
323
+ let endIndex = findHydrationEndIndex(nodes, i);
324
+ invariant(endIndex > i, 'Hydration end marker not found');
325
+ let end = nodes[endIndex];
326
+ invariant(isVirtualRootEndMarker(end), 'Expected hydration end marker');
327
+ units.push({ kind: 'hydration', start: node, end, startIndex: i, endIndex });
328
+ i = endIndex;
329
+ continue;
357
330
  }
331
+ if (isFrameStartMarker(node)) {
332
+ let endIndex = findFrameEndIndex(nodes, i);
333
+ invariant(endIndex > i, 'Frame end marker not found');
334
+ let end = nodes[endIndex];
335
+ invariant(isFrameEndMarker(end), 'Expected frame end marker');
336
+ units.push({ kind: 'frame', start: node, end, startIndex: i, endIndex });
337
+ i = endIndex;
338
+ continue;
339
+ }
340
+ invariant(!isVirtualRootEndMarker(node), 'Unexpected hydration end marker');
341
+ invariant(!isFrameEndMarker(node), 'Unexpected frame end marker');
342
+ units.push({ kind: 'node', node, startIndex: i, endIndex: i });
358
343
  }
359
- // Remove any current children not used
360
- for (let i = 0; i < currentChildren.length; i++) {
361
- if (!used[i]) {
362
- let nodeToRemove = currentChildren[i];
363
- removeNode(nodeToRemove, current, context);
344
+ return units;
345
+ }
346
+ function getSiblingUnitKey(unit) {
347
+ if (unit.kind !== 'node' || !isElement(unit.node))
348
+ return;
349
+ return unit.node.getAttribute('data-key') ?? undefined;
350
+ }
351
+ function siblingUnitsComparable(current, next) {
352
+ if (current.kind !== next.kind)
353
+ return false;
354
+ if (current.kind !== 'node' || next.kind !== 'node')
355
+ return true;
356
+ return nodeTypesComparable(current.node, next.node);
357
+ }
358
+ function shouldPreserveBoundaryUnit(current, next, context) {
359
+ if (current.kind !== next.kind)
360
+ return false;
361
+ return current.kind === 'frame'
362
+ ? shouldPreserveFrameStartMarker(current.start, next.start, context)
363
+ : shouldPreserveHydrationStartMarker(current.start, next.start, context);
364
+ }
365
+ function getSiblingUnitFirstNode(unit) {
366
+ return unit.kind === 'node' ? unit.node : unit.start;
367
+ }
368
+ function placeSiblingUnitBefore(unit, parent, ref) {
369
+ if (unit.kind === 'node') {
370
+ if (unit.node.parentNode !== parent || unit.node.nextSibling !== ref) {
371
+ parent.insertBefore(unit.node, ref);
364
372
  }
373
+ return;
374
+ }
375
+ let nodes = collectNodeRange(unit.start, unit.end);
376
+ if (unit.start.parentNode === parent && unit.end.nextSibling === ref)
377
+ return;
378
+ let fragment = unit.start.ownerDocument.createDocumentFragment();
379
+ for (let node of nodes)
380
+ fragment.appendChild(node);
381
+ parent.insertBefore(fragment, ref);
382
+ }
383
+ function removeSiblingUnit(unit, parent, context) {
384
+ if (unit.kind === 'node') {
385
+ removeNode(unit.node, parent, context);
386
+ return;
387
+ }
388
+ for (let node of collectNodeRange(unit.start, unit.end)) {
389
+ removeNode(node, parent, context);
390
+ }
391
+ }
392
+ function collectNodesBetween(start, end) {
393
+ let nodes = [];
394
+ let node = start.nextSibling;
395
+ while (node && node !== end) {
396
+ nodes.push(node);
397
+ node = node.nextSibling;
365
398
  }
399
+ return nodes;
400
+ }
401
+ function isPreservedDomElement(node) {
402
+ return isElement(node) && node.hasAttribute(REMIX_PRESERVE_DOM_ATTRIBUTE);
366
403
  }
367
404
  function nodeTypesComparable(a, b) {
368
405
  if (isTextNode(a) && isTextNode(b))
369
406
  return true;
370
407
  if (isElement(a) && isElement(b))
371
408
  return a.tagName === b.tagName;
372
- if (isVirtualRootStartMarker(a) && isVirtualRootStartMarker(b))
373
- return true;
374
- if (isVirtualRootEndMarker(a) && isVirtualRootEndMarker(b))
375
- return true;
376
409
  if (isCommentNode(a) && isCommentNode(b))
377
- return true;
410
+ return markerKindsMatch(a, b);
378
411
  return false;
379
412
  }
413
+ function getMarkerKind(node) {
414
+ if (!isCommentNode(node))
415
+ return undefined;
416
+ if (isFrameStartMarker(node))
417
+ return 'frame-start';
418
+ if (isFrameEndMarker(node))
419
+ return 'frame-end';
420
+ if (isVirtualRootStartMarker(node))
421
+ return 'virtual-root-start';
422
+ if (isVirtualRootEndMarker(node))
423
+ return 'virtual-root-end';
424
+ return undefined;
425
+ }
426
+ function markerKindsMatch(a, b) {
427
+ return getMarkerKind(a) === getMarkerKind(b);
428
+ }
380
429
  function isHydrationEndComment(node) {
381
430
  return isCommentNode(node) && node.data.trim() === '/rmx:h';
382
431
  }
@@ -398,9 +447,16 @@ function findHydrationEndMarker(start) {
398
447
  throw new Error('Hydration end marker not found');
399
448
  }
400
449
  function findHydrationEndIndex(nodes, startIdx) {
450
+ let depth = 1;
401
451
  for (let j = startIdx + 1; j < nodes.length; j++) {
402
- if (isHydrationEndComment(nodes[j]))
403
- return j;
452
+ let node = nodes[j];
453
+ if (isVirtualRootStartMarker(node))
454
+ depth++;
455
+ if (isHydrationEndComment(node)) {
456
+ depth--;
457
+ if (depth === 0)
458
+ return j;
459
+ }
404
460
  }
405
461
  return startIdx;
406
462
  }
@@ -451,12 +507,21 @@ function isFrameEndMarker(node) {
451
507
  function shouldPreserveFrameStartMarker(current, next, context) {
452
508
  if (!isFrameStartMarker(next))
453
509
  return false;
454
- let currentData = getFrameMarkerData(current, context);
455
510
  let nextData = getFrameMarkerData(next, context);
456
- return (currentData !== undefined &&
511
+ let currentFrame = context.frameInstances.get(current);
512
+ return (currentFrame !== undefined &&
457
513
  nextData !== undefined &&
458
- currentData.src === nextData.src &&
459
- currentData.name === nextData.name);
514
+ currentFrame.matchesIdentity(nextData.src, nextData.name));
515
+ }
516
+ function shouldPreserveHydrationStartMarker(current, next, context) {
517
+ if (!isVirtualRootStartMarker(next))
518
+ return false;
519
+ let currentOwner = getClientEntryBoundaryOwner(current);
520
+ let nextData = getHydrationMarkerData(next, context);
521
+ return (currentOwner !== undefined &&
522
+ nextData !== undefined &&
523
+ currentOwner.identity.moduleUrl === nextData.moduleUrl &&
524
+ currentOwner.identity.exportName === nextData.exportName);
460
525
  }
461
526
  function getCommentMarkerRangeReplacement(current, next, currentNodes, nextNodes, currentIndex, nextIndex, context) {
462
527
  // Comment markers can represent owned DOM ranges, not standalone comments. If
@@ -472,6 +537,25 @@ function getCommentMarkerRangeReplacement(current, next, currentNodes, nextNodes
472
537
  nextEndIndex: findFrameEndIndex(nextNodes, nextIndex),
473
538
  };
474
539
  }
540
+ if (isVirtualRootStartMarker(current) &&
541
+ isVirtualRootStartMarker(next) &&
542
+ !shouldPreserveHydrationStartMarker(current, next, context)) {
543
+ return {
544
+ currentStart: current,
545
+ nextStart: next,
546
+ currentEndIndex: findHydrationEndIndex(currentNodes, currentIndex),
547
+ nextEndIndex: findHydrationEndIndex(nextNodes, nextIndex),
548
+ };
549
+ }
550
+ }
551
+ function getHydrationMarkerData(marker, context) {
552
+ let id = getHydrationId(marker);
553
+ return context.data.h?.[id];
554
+ }
555
+ function getHydrationId(marker) {
556
+ let trimmed = marker.data.trim();
557
+ invariant(trimmed.startsWith('rmx:h:'), 'Invalid hydration start marker');
558
+ return trimmed.slice('rmx:h:'.length);
475
559
  }
476
560
  function getFrameMarkerData(marker, context) {
477
561
  let id = getFrameId(marker);
@@ -483,8 +567,8 @@ function getFrameId(marker) {
483
567
  return trimmed.slice('rmx:f:'.length);
484
568
  }
485
569
  function replaceCommentMarkerRange(replacement, parent, context) {
486
- let currentEnd = findFrameEndMarker(replacement.currentStart);
487
- let nextEnd = findFrameEndMarker(replacement.nextStart);
570
+ let currentEnd = findCommentMarkerRangeEnd(replacement.currentStart);
571
+ let nextEnd = findCommentMarkerRangeEnd(replacement.nextStart);
488
572
  let nextNodes = collectNodeRange(replacement.nextStart, nextEnd);
489
573
  let currentNodes = collectNodeRange(replacement.currentStart, currentEnd);
490
574
  for (let node of nextNodes) {
@@ -494,6 +578,13 @@ function replaceCommentMarkerRange(replacement, parent, context) {
494
578
  removeNode(node, parent, context);
495
579
  }
496
580
  }
581
+ function findCommentMarkerRangeEnd(start) {
582
+ if (isFrameStartMarker(start))
583
+ return findFrameEndMarker(start);
584
+ if (isVirtualRootStartMarker(start))
585
+ return findHydrationEndMarker(start);
586
+ throw new Error('Comment marker range start not found');
587
+ }
497
588
  function collectNodeRange(start, end) {
498
589
  let nodes = [];
499
590
  let node = start;
@@ -528,8 +619,7 @@ function disposeRemovedVirtualRoots(node) {
528
619
  let next = stack.pop();
529
620
  if (!next)
530
621
  continue;
531
- if (isHydratedVirtualRootStartMarker(next)) {
532
- next.$rmx.dispose();
622
+ if (isVirtualRootStartMarker(next) && disposeClientEntryBoundary(next)) {
533
623
  continue;
534
624
  }
535
625
  for (let child of Array.from(next.childNodes)) {
@@ -561,9 +651,6 @@ function disposeFrameStartMarker(marker, context) {
561
651
  function isVirtualRootStartMarker(node) {
562
652
  return isCommentNode(node) && node.data.trim().startsWith('rmx:h:');
563
653
  }
564
- function isHydratedVirtualRootStartMarker(node) {
565
- return isVirtualRootStartMarker(node) && '$rmx' in node;
566
- }
567
654
  function isVirtualRootEndMarker(node) {
568
655
  return isCommentNode(node) && node.data.trim() === '/rmx:h';
569
656
  }