rask-ui 0.2.5 → 0.2.7

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/dist/vdom/AbstractVNode.d.ts +20 -1
  2. package/dist/vdom/AbstractVNode.d.ts.map +1 -1
  3. package/dist/vdom/AbstractVNode.js +151 -12
  4. package/dist/vdom/ComponentVNode.d.ts +2 -2
  5. package/dist/vdom/ComponentVNode.d.ts.map +1 -1
  6. package/dist/vdom/ComponentVNode.js +17 -8
  7. package/dist/vdom/ElementVNode.d.ts +6 -9
  8. package/dist/vdom/ElementVNode.d.ts.map +1 -1
  9. package/dist/vdom/ElementVNode.js +103 -34
  10. package/dist/vdom/FragmentVNode.d.ts +2 -2
  11. package/dist/vdom/FragmentVNode.d.ts.map +1 -1
  12. package/dist/vdom/FragmentVNode.js +18 -7
  13. package/dist/vdom/RootVNode.d.ts +2 -2
  14. package/dist/vdom/RootVNode.d.ts.map +1 -1
  15. package/dist/vdom/RootVNode.js +15 -6
  16. package/dist/vdom/dom-utils.d.ts +6 -1
  17. package/dist/vdom/dom-utils.d.ts.map +1 -1
  18. package/dist/vdom/dom-utils.js +20 -1
  19. package/dist/vdom/types.d.ts +12 -0
  20. package/dist/vdom/types.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/dist/component.d.ts +0 -38
  23. package/dist/component.d.ts.map +0 -1
  24. package/dist/component.js +0 -130
  25. package/dist/context.d.ts +0 -5
  26. package/dist/context.d.ts.map +0 -1
  27. package/dist/context.js +0 -29
  28. package/dist/createAsync.test.d.ts +0 -2
  29. package/dist/createAsync.test.d.ts.map +0 -1
  30. package/dist/createAsync.test.js +0 -110
  31. package/dist/createAsyncState.d.ts +0 -16
  32. package/dist/createAsyncState.d.ts.map +0 -1
  33. package/dist/createAsyncState.js +0 -24
  34. package/dist/createContext.test.d.ts +0 -2
  35. package/dist/createContext.test.d.ts.map +0 -1
  36. package/dist/createContext.test.js +0 -136
  37. package/dist/createMutation.test.d.ts +0 -2
  38. package/dist/createMutation.test.d.ts.map +0 -1
  39. package/dist/createMutation.test.js +0 -168
  40. package/dist/createQuery.test.d.ts +0 -2
  41. package/dist/createQuery.test.d.ts.map +0 -1
  42. package/dist/createQuery.test.js +0 -156
  43. package/dist/createRef.test.d.ts +0 -2
  44. package/dist/createRef.test.d.ts.map +0 -1
  45. package/dist/createRef.test.js +0 -80
  46. package/dist/createState.test.d.ts +0 -2
  47. package/dist/createState.test.d.ts.map +0 -1
  48. package/dist/createState.test.js +0 -111
  49. package/dist/createView.test.d.ts +0 -2
  50. package/dist/createView.test.d.ts.map +0 -1
  51. package/dist/createView.test.js +0 -203
  52. package/dist/error.test.d.ts +0 -2
  53. package/dist/error.test.d.ts.map +0 -1
  54. package/dist/error.test.js +0 -144
  55. package/dist/integration.test.d.ts +0 -2
  56. package/dist/integration.test.d.ts.map +0 -1
  57. package/dist/integration.test.js +0 -155
  58. package/dist/jsx.d.ts.map +0 -1
  59. package/dist/jsx.js +0 -42
  60. package/dist/observation.test.d.ts +0 -2
  61. package/dist/observation.test.d.ts.map +0 -1
  62. package/dist/observation.test.js +0 -113
  63. package/dist/render-test.d.ts +0 -2
  64. package/dist/render-test.d.ts.map +0 -1
  65. package/dist/render-test.js +0 -21
  66. package/dist/render.d.ts +0 -7
  67. package/dist/render.d.ts.map +0 -1
  68. package/dist/render.js +0 -77
  69. package/dist/suspense.d.ts +0 -25
  70. package/dist/suspense.d.ts.map +0 -1
  71. package/dist/suspense.js +0 -97
  72. package/dist/tests/class.test.d.ts +0 -2
  73. package/dist/tests/class.test.d.ts.map +0 -1
  74. package/dist/tests/class.test.js +0 -185
  75. package/dist/tests/complex-rendering.test.d.ts +0 -2
  76. package/dist/tests/complex-rendering.test.d.ts.map +0 -1
  77. package/dist/tests/complex-rendering.test.js +0 -400
  78. package/dist/tests/component.cleanup.test.d.ts +0 -2
  79. package/dist/tests/component.cleanup.test.d.ts.map +0 -1
  80. package/dist/tests/component.cleanup.test.js +0 -325
  81. package/dist/tests/component.counter.test.d.ts +0 -2
  82. package/dist/tests/component.counter.test.d.ts.map +0 -1
  83. package/dist/tests/component.counter.test.js +0 -124
  84. package/dist/tests/component.interaction.test.d.ts +0 -2
  85. package/dist/tests/component.interaction.test.d.ts.map +0 -1
  86. package/dist/tests/component.interaction.test.js +0 -73
  87. package/dist/tests/component.props.test.d.ts +0 -2
  88. package/dist/tests/component.props.test.d.ts.map +0 -1
  89. package/dist/tests/component.props.test.js +0 -334
  90. package/dist/tests/component.return-types.test.d.ts +0 -2
  91. package/dist/tests/component.return-types.test.d.ts.map +0 -1
  92. package/dist/tests/component.return-types.test.js +0 -357
  93. package/dist/tests/component.state.test.d.ts +0 -2
  94. package/dist/tests/component.state.test.d.ts.map +0 -1
  95. package/dist/tests/component.state.test.js +0 -135
  96. package/dist/tests/component.test.d.ts +0 -2
  97. package/dist/tests/component.test.d.ts.map +0 -1
  98. package/dist/tests/component.test.js +0 -63
  99. package/dist/tests/createAsync.test.d.ts +0 -2
  100. package/dist/tests/createAsync.test.d.ts.map +0 -1
  101. package/dist/tests/createAsync.test.js +0 -110
  102. package/dist/tests/createContext.test.d.ts +0 -2
  103. package/dist/tests/createContext.test.d.ts.map +0 -1
  104. package/dist/tests/createContext.test.js +0 -141
  105. package/dist/tests/createMutation.test.d.ts +0 -2
  106. package/dist/tests/createMutation.test.d.ts.map +0 -1
  107. package/dist/tests/createMutation.test.js +0 -168
  108. package/dist/tests/createQuery.test.d.ts +0 -2
  109. package/dist/tests/createQuery.test.d.ts.map +0 -1
  110. package/dist/tests/createQuery.test.js +0 -156
  111. package/dist/tests/createRef.test.d.ts +0 -2
  112. package/dist/tests/createRef.test.d.ts.map +0 -1
  113. package/dist/tests/createRef.test.js +0 -84
  114. package/dist/tests/createState.test.d.ts +0 -2
  115. package/dist/tests/createState.test.d.ts.map +0 -1
  116. package/dist/tests/createState.test.js +0 -103
  117. package/dist/tests/createView.test.d.ts +0 -2
  118. package/dist/tests/createView.test.d.ts.map +0 -1
  119. package/dist/tests/createView.test.js +0 -203
  120. package/dist/tests/edge-cases.test.d.ts +0 -2
  121. package/dist/tests/edge-cases.test.d.ts.map +0 -1
  122. package/dist/tests/edge-cases.test.js +0 -637
  123. package/dist/tests/error-no-boundary.test.d.ts +0 -2
  124. package/dist/tests/error-no-boundary.test.d.ts.map +0 -1
  125. package/dist/tests/error-no-boundary.test.js +0 -174
  126. package/dist/tests/error.test.d.ts +0 -2
  127. package/dist/tests/error.test.d.ts.map +0 -1
  128. package/dist/tests/error.test.js +0 -199
  129. package/dist/tests/fragment.test.d.ts +0 -2
  130. package/dist/tests/fragment.test.d.ts.map +0 -1
  131. package/dist/tests/fragment.test.js +0 -618
  132. package/dist/tests/integration.test.d.ts +0 -2
  133. package/dist/tests/integration.test.d.ts.map +0 -1
  134. package/dist/tests/integration.test.js +0 -192
  135. package/dist/tests/keys.test.d.ts +0 -2
  136. package/dist/tests/keys.test.d.ts.map +0 -1
  137. package/dist/tests/keys.test.js +0 -293
  138. package/dist/tests/mount.test.d.ts +0 -2
  139. package/dist/tests/mount.test.d.ts.map +0 -1
  140. package/dist/tests/mount.test.js +0 -91
  141. package/dist/tests/observation.test.d.ts +0 -2
  142. package/dist/tests/observation.test.d.ts.map +0 -1
  143. package/dist/tests/observation.test.js +0 -113
  144. package/dist/tests/patch.test.d.ts +0 -2
  145. package/dist/tests/patch.test.d.ts.map +0 -1
  146. package/dist/tests/patch.test.js +0 -498
  147. package/dist/tests/patchChildren.test.d.ts +0 -2
  148. package/dist/tests/patchChildren.test.d.ts.map +0 -1
  149. package/dist/tests/patchChildren.test.js +0 -405
  150. package/dist/tests/primitives.test.d.ts +0 -2
  151. package/dist/tests/primitives.test.d.ts.map +0 -1
  152. package/dist/tests/primitives.test.js +0 -132
  153. package/dist/vdom/class.test.d.ts +0 -2
  154. package/dist/vdom/class.test.d.ts.map +0 -1
  155. package/dist/vdom/class.test.js +0 -143
  156. package/dist/vdom/complex-rendering.test.d.ts +0 -2
  157. package/dist/vdom/complex-rendering.test.d.ts.map +0 -1
  158. package/dist/vdom/complex-rendering.test.js +0 -400
  159. package/dist/vdom/component.cleanup.test.d.ts +0 -2
  160. package/dist/vdom/component.cleanup.test.d.ts.map +0 -1
  161. package/dist/vdom/component.cleanup.test.js +0 -323
  162. package/dist/vdom/component.counter.test.d.ts +0 -2
  163. package/dist/vdom/component.counter.test.d.ts.map +0 -1
  164. package/dist/vdom/component.counter.test.js +0 -124
  165. package/dist/vdom/component.interaction.test.d.ts +0 -2
  166. package/dist/vdom/component.interaction.test.d.ts.map +0 -1
  167. package/dist/vdom/component.interaction.test.js +0 -73
  168. package/dist/vdom/component.props.test.d.ts +0 -2
  169. package/dist/vdom/component.props.test.d.ts.map +0 -1
  170. package/dist/vdom/component.props.test.js +0 -88
  171. package/dist/vdom/component.return-types.test.d.ts +0 -2
  172. package/dist/vdom/component.return-types.test.d.ts.map +0 -1
  173. package/dist/vdom/component.return-types.test.js +0 -357
  174. package/dist/vdom/component.state.test.d.ts +0 -2
  175. package/dist/vdom/component.state.test.d.ts.map +0 -1
  176. package/dist/vdom/component.state.test.js +0 -129
  177. package/dist/vdom/component.test.d.ts +0 -2
  178. package/dist/vdom/component.test.d.ts.map +0 -1
  179. package/dist/vdom/component.test.js +0 -63
  180. package/dist/vdom/edge-cases.test.d.ts +0 -2
  181. package/dist/vdom/edge-cases.test.d.ts.map +0 -1
  182. package/dist/vdom/edge-cases.test.js +0 -637
  183. package/dist/vdom/fragment.test.d.ts +0 -2
  184. package/dist/vdom/fragment.test.d.ts.map +0 -1
  185. package/dist/vdom/fragment.test.js +0 -618
  186. package/dist/vdom/keys.test.d.ts +0 -2
  187. package/dist/vdom/keys.test.d.ts.map +0 -1
  188. package/dist/vdom/keys.test.js +0 -293
  189. package/dist/vdom/mount.test.d.ts +0 -2
  190. package/dist/vdom/mount.test.d.ts.map +0 -1
  191. package/dist/vdom/mount.test.js +0 -91
  192. package/dist/vdom/patch.test.d.ts +0 -2
  193. package/dist/vdom/patch.test.d.ts.map +0 -1
  194. package/dist/vdom/patch.test.js +0 -498
  195. package/dist/vdom/patchChildren.test.d.ts +0 -2
  196. package/dist/vdom/patchChildren.test.d.ts.map +0 -1
  197. package/dist/vdom/patchChildren.test.js +0 -392
  198. package/dist/vdom/primitives.test.d.ts +0 -2
  199. package/dist/vdom/primitives.test.d.ts.map +0 -1
  200. package/dist/vdom/primitives.test.js +0 -132
@@ -1,5 +1,16 @@
1
1
  import { RootVNode } from "./RootVNode";
2
2
  import { VNode } from "./types";
3
+ export type PatchOperation = {
4
+ type: "add";
5
+ elm: Node | Node[];
6
+ } | {
7
+ type: "replace";
8
+ oldElm: Node | Node[];
9
+ newElm: Node | Node[];
10
+ } | {
11
+ type: "remove";
12
+ elm: Node | Node[];
13
+ };
3
14
  export declare abstract class AbstractVNode {
4
15
  key?: string;
5
16
  parent?: VNode;
@@ -9,7 +20,7 @@ export declare abstract class AbstractVNode {
9
20
  abstract mount(parent?: VNode): Node | Node[];
10
21
  abstract patch(oldNode: VNode): void;
11
22
  abstract unmount(): void;
12
- abstract rerender(): void;
23
+ abstract rerender(operations?: PatchOperation[]): void;
13
24
  protected getHTMLElement(): HTMLElement;
14
25
  /**
15
26
  * A VNode can represent multiple elements (fragment of component)
@@ -20,6 +31,14 @@ export declare abstract class AbstractVNode {
20
31
  patchChildren(newChildren: VNode[]): {
21
32
  children: VNode[];
22
33
  hasChangedStructure: boolean;
34
+ operations: PatchOperation[];
23
35
  };
36
+ applyPatchOperations(target: HTMLElement, operations: PatchOperation[]): void;
37
+ /**
38
+ * Intelligently sync DOM to match children VNode order.
39
+ * Only performs DOM operations when elements are out of position.
40
+ * This is used by both patch() and rerender() to efficiently update children.
41
+ */
42
+ protected syncDOMChildren(): void;
24
43
  }
25
44
  //# sourceMappingURL=AbstractVNode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/AbstractVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,8BAAsB,aAAa;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE;IAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI;IACpC,QAAQ,CAAC,OAAO,IAAI,IAAI;IACxB,QAAQ,CAAC,QAAQ,IAAI,IAAI;IACzB,SAAS,CAAC,cAAc;IAOxB;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAWrB,gBAAgB,IAAI,WAAW;IAiB/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAG,OAAO;IAoB3D,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG;QACnC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,mBAAmB,EAAE,OAAO,CAAC;KAC9B;CAqEF"}
1
+ {"version":3,"file":"AbstractVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/AbstractVNode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACpB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;IACtB,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACvB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC;CACpB,CAAC;AAEN,8BAAsB,aAAa;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE;IAC7C,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI;IACpC,QAAQ,CAAC,OAAO,IAAI,IAAI;IACxB,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IACtD,SAAS,CAAC,cAAc;IAOxB;;OAEG;IACH,WAAW,IAAI,IAAI,EAAE;IAmBrB,gBAAgB,IAAI,WAAW;IAiB/B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAG,OAAO;IAoB3D,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG;QACnC,QAAQ,EAAE,KAAK,EAAE,CAAC;QAClB,mBAAmB,EAAE,OAAO,CAAC;QAC7B,UAAU,EAAE,cAAc,EAAE,CAAC;KAC9B;IAkJD,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE;IAqCtE;;;;OAIG;IACH,SAAS,CAAC,eAAe;CA6B1B"}
@@ -1,3 +1,4 @@
1
+ import { elementsToFragment } from "./dom-utils";
1
2
  export class AbstractVNode {
2
3
  key;
3
4
  parent;
@@ -20,7 +21,15 @@ export class AbstractVNode {
20
21
  if (!this.children) {
21
22
  throw new Error("This VNode has no element or children");
22
23
  }
23
- return this.children.map((child) => child.getElements()).flat();
24
+ // Optimized: avoid intermediate arrays from map+flat
25
+ const result = [];
26
+ for (let i = 0; i < this.children.length; i++) {
27
+ const childElms = this.children[i].getElements();
28
+ for (let j = 0; j < childElms.length; j++) {
29
+ result.push(childElms[j]);
30
+ }
31
+ }
32
+ return result;
24
33
  }
25
34
  getParentElement() {
26
35
  let parent = this.parent;
@@ -57,12 +66,16 @@ export class AbstractVNode {
57
66
  // When there are only new children, we just mount them
58
67
  if (newChildren && prevChildren.length === 0) {
59
68
  newChildren.forEach((child) => child.mount(this));
60
- return { children: newChildren, hasChangedStructure: true };
69
+ return {
70
+ children: newChildren,
71
+ hasChangedStructure: true,
72
+ operations: [],
73
+ };
61
74
  }
62
75
  // If we want to remove all children, we just unmount the previous ones
63
76
  if (!newChildren.length && prevChildren.length) {
64
77
  prevChildren.forEach((child) => child.unmount());
65
- return { children: [], hasChangedStructure: true };
78
+ return { children: [], hasChangedStructure: true, operations: [] };
66
79
  }
67
80
  const oldKeys = {};
68
81
  prevChildren.forEach((prevChild, index) => {
@@ -73,43 +86,169 @@ export class AbstractVNode {
73
86
  });
74
87
  // Build result array in the NEW order
75
88
  const result = [];
76
- let hasChangedStructure = false;
89
+ const operations = [];
90
+ // Track indices of reused nodes in their original order
91
+ const reusedOldIndices = [];
92
+ // Track which result positions contain new nodes (not reused/replaced)
93
+ const isNewNode = [];
94
+ let forceStructuralChange = false;
77
95
  newChildren.forEach((newChild, index) => {
78
96
  const key = newChild.key || index;
79
97
  const prevChild = oldKeys[key];
80
98
  if (!prevChild) {
81
99
  // New child - mount and add to result
82
- newChild.mount(this);
100
+ const elm = newChild.mount(this);
83
101
  result.push(newChild);
84
- hasChangedStructure = true;
102
+ isNewNode.push(true);
103
+ operations.push({ type: "add", elm });
85
104
  }
86
105
  else if (prevChild?.vnode === newChild) {
87
106
  // Same instance - no patching needed, just reuse
88
107
  result.push(prevChild.vnode);
108
+ isNewNode.push(false);
109
+ reusedOldIndices.push(prevChild.index);
89
110
  delete oldKeys[key];
90
- hasChangedStructure = hasChangedStructure || prevChild.index !== index;
91
111
  }
92
112
  else if (this.canPatch(prevChild.vnode, newChild)) {
93
113
  // Compatible types - patch and reuse old VNode
94
114
  prevChild.vnode.patch(newChild);
95
115
  result.push(prevChild.vnode);
116
+ isNewNode.push(false);
117
+ reusedOldIndices.push(prevChild.index);
96
118
  delete oldKeys[key];
97
- hasChangedStructure = hasChangedStructure || prevChild.index !== index;
98
119
  }
99
120
  else {
100
121
  // Incompatible types - replace completely
101
- newChild.mount(this);
122
+ const newElm = newChild.mount(this);
102
123
  prevChild.vnode.unmount();
103
124
  result.push(newChild);
125
+ isNewNode.push(false); // Replacement, not an insertion
104
126
  delete oldKeys[key];
105
- hasChangedStructure = true;
127
+ const oldElm = prevChild.vnode.getElements();
128
+ // We need to fall back to structural change when the old node does
129
+ // not have any elements. This can happen when a component returns null,
130
+ // throws an error etc.
131
+ if (!oldElm.length) {
132
+ forceStructuralChange = true;
133
+ return;
134
+ }
135
+ operations.push({
136
+ type: "replace",
137
+ oldElm,
138
+ newElm,
139
+ });
106
140
  }
107
141
  });
108
142
  // Unmount any old children that weren't reused
109
143
  for (const key in oldKeys) {
110
144
  oldKeys[key].vnode.unmount();
111
- hasChangedStructure = true;
145
+ operations.push({
146
+ type: "remove",
147
+ elm: oldKeys[key].vnode.getElements(),
148
+ });
149
+ }
150
+ // Detect structural changes:
151
+ // 1. Reordering: reused nodes are not in their original relative order
152
+ // 2. Insertion in middle: new nodes inserted before existing nodes
153
+ let hasReordering = false;
154
+ let hasInsertionInMiddle = false;
155
+ if (!forceStructuralChange) {
156
+ for (let i = 1; i < reusedOldIndices.length; i++) {
157
+ if (reusedOldIndices[i] < reusedOldIndices[i - 1]) {
158
+ hasReordering = true;
159
+ break;
160
+ }
161
+ }
162
+ }
163
+ if (!hasReordering) {
164
+ // Find the last position that contains a reused/replaced node
165
+ let lastReusedResultIndex = -1;
166
+ for (let i = result.length - 1; i >= 0; i--) {
167
+ if (!isNewNode[i]) {
168
+ lastReusedResultIndex = i;
169
+ break;
170
+ }
171
+ }
172
+ // Check if any new nodes were inserted before the last reused/replaced node
173
+ if (lastReusedResultIndex >= 0) {
174
+ for (let i = 0; i < lastReusedResultIndex; i++) {
175
+ if (isNewNode[i]) {
176
+ hasInsertionInMiddle = true;
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ }
182
+ const hasChangedStructure = forceStructuralChange || hasReordering || hasInsertionInMiddle;
183
+ if (hasChangedStructure) {
184
+ operations.length = 0;
185
+ }
186
+ return { children: result, hasChangedStructure, operations };
187
+ }
188
+ applyPatchOperations(target, operations) {
189
+ operations.forEach((operation) => {
190
+ switch (operation.type) {
191
+ case "add": {
192
+ target.appendChild(elementsToFragment(operation.elm));
193
+ break;
194
+ }
195
+ case "remove": {
196
+ if (Array.isArray(operation.elm)) {
197
+ const range = new Range();
198
+ range.setStartBefore(operation.elm[0]);
199
+ range.setEndAfter(operation.elm[operation.elm.length - 1]);
200
+ range.deleteContents();
201
+ }
202
+ else {
203
+ target.removeChild(operation.elm);
204
+ }
205
+ break;
206
+ }
207
+ case "replace": {
208
+ if (Array.isArray(operation.oldElm)) {
209
+ const range = new Range();
210
+ range.setStartBefore(operation.oldElm[0]);
211
+ range.setEndAfter(operation.oldElm[operation.oldElm.length - 1]);
212
+ range.deleteContents();
213
+ range.insertNode(elementsToFragment(operation.newElm));
214
+ }
215
+ else {
216
+ target.replaceChild(elementsToFragment(operation.newElm), operation.oldElm);
217
+ }
218
+ break;
219
+ }
220
+ }
221
+ });
222
+ }
223
+ /**
224
+ * Intelligently sync DOM to match children VNode order.
225
+ * Only performs DOM operations when elements are out of position.
226
+ * This is used by both patch() and rerender() to efficiently update children.
227
+ */
228
+ syncDOMChildren() {
229
+ if (!this.children) {
230
+ return;
231
+ }
232
+ const elm = this.elm;
233
+ let currentDomChild = elm.firstChild;
234
+ for (const child of this.children) {
235
+ const childNodes = child.getElements();
236
+ for (const node of childNodes) {
237
+ if (currentDomChild === node) {
238
+ // Already in correct position, advance pointer
239
+ currentDomChild = currentDomChild.nextSibling;
240
+ }
241
+ else {
242
+ // Insert (or move if it exists elsewhere in DOM)
243
+ elm.insertBefore(node, currentDomChild);
244
+ }
245
+ }
246
+ }
247
+ // Remove any leftover nodes (shouldn't happen if unmount works correctly)
248
+ while (currentDomChild) {
249
+ const next = currentDomChild.nextSibling;
250
+ elm.removeChild(currentDomChild);
251
+ currentDomChild = next;
112
252
  }
113
- return { children: result, hasChangedStructure };
114
253
  }
115
254
  }
@@ -1,5 +1,5 @@
1
1
  import { Observer } from "../observation";
2
- import { AbstractVNode } from "./AbstractVNode";
2
+ import { AbstractVNode, PatchOperation } from "./AbstractVNode";
3
3
  import { Props, VNode } from "./types";
4
4
  export type ComponentChild = VNode | string | null | number | undefined | boolean;
5
5
  export type ComponentChildren = ComponentChild | ComponentChild[];
@@ -40,7 +40,7 @@ export declare class ComponentVNode extends AbstractVNode {
40
40
  children: VNode[];
41
41
  instance?: ComponentInstance;
42
42
  constructor(component: Component<any>, props: Props, children: VNode[], key?: string);
43
- rerender(): void;
43
+ rerender(operations?: PatchOperation[]): void;
44
44
  mount(parent?: VNode): Node[];
45
45
  patch(newNode: ComponentVNode): void;
46
46
  unmount(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"ComponentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ComponentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,IAAI,GACJ,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,KAAK,IACjC,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,iBAAiB,CAAC,GACvC,CAAC,MAAM,MAAM,iBAAiB,CAAC,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC,CAAC;AAKF,wBAAgB,mBAAmB,sBAYlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAYrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAYvC;AAED,qBAAa,cAAe,SAAQ,aAAa;IAC/C,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,QAAQ,EAAE,KAAK,EAAE,CAAM;IACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;gBAE3B,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAWd,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAmI7B,KAAK,CAAC,OAAO,EAAE,cAAc;IAW7B,OAAO;CAcR"}
1
+ {"version":3,"file":"ComponentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ComponentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,MAAM,cAAc,GACtB,KAAK,GACL,MAAM,GACN,IAAI,GACJ,MAAM,GACN,SAAS,GACT,OAAO,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,cAAc,EAAE,CAAC;AAElE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,KAAK,IACjC,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,iBAAiB,CAAC,GACvC,CAAC,MAAM,MAAM,iBAAiB,CAAC,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC,CAAC;AAKF,wBAAgB,mBAAmB,sBAYlC;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,QAYrC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAYvC;AAED,qBAAa,cAAe,SAAQ,aAAa;IAC/C,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,KAAK,EAAE,KAAK,CAAC;IAEb,QAAQ,EAAE,KAAK,EAAE,CAAM;IACvB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;gBAE3B,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAWd,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAG7C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IA6I7B,KAAK,CAAC,OAAO,EAAE,cAAc;IAW7B,OAAO;CAcR"}
@@ -3,6 +3,7 @@ import { AbstractVNode } from "./AbstractVNode";
3
3
  import { FragmentVNode } from "./FragmentVNode";
4
4
  import { RootVNode } from "./RootVNode";
5
5
  import { normalizeChildren } from "./utils";
6
+ import { flattenNodes } from "./dom-utils";
6
7
  import { currentRoot } from "./RootVNode";
7
8
  export function getCurrentComponent() {
8
9
  if (!currentRoot) {
@@ -50,8 +51,8 @@ export class ComponentVNode extends AbstractVNode {
50
51
  this.children = [];
51
52
  this.key = key;
52
53
  }
53
- rerender() {
54
- this.parent?.rerender();
54
+ rerender(operations) {
55
+ this.parent?.rerender(operations);
55
56
  }
56
57
  mount(parent) {
57
58
  this.parent = parent;
@@ -95,12 +96,17 @@ export class ComponentVNode extends AbstractVNode {
95
96
  isObserverQueued = false;
96
97
  this.root?.setAsCurrent();
97
98
  const newChildren = executeRender();
98
- const prevChildren = this.children;
99
- const { children, hasChangedStructure } = this.patchChildren(newChildren);
99
+ const { children, hasChangedStructure, operations } = this.patchChildren(newChildren);
100
100
  this.children = children;
101
- if (hasChangedStructure) {
101
+ // So if a fragment is returned where we add new elements we can not safely
102
+ // add them yet, check Fragment for a potential later optimization
103
+ const hasAddOperation = operations.some((operation) => operation.type === "add");
104
+ if (hasChangedStructure || hasAddOperation) {
102
105
  this.parent?.rerender();
103
106
  }
107
+ else if (operations.length) {
108
+ this.parent?.rerender(operations);
109
+ }
104
110
  this.root?.clearCurrent();
105
111
  });
106
112
  }),
@@ -153,9 +159,12 @@ export class ComponentVNode extends AbstractVNode {
153
159
  this.children = executeRender();
154
160
  this.root?.popComponent();
155
161
  this.root?.clearCurrent();
156
- const childElements = this.children
157
- .map((child) => child.mount(this))
158
- .flat();
162
+ // Optimized: avoid intermediate arrays from map+flat
163
+ const childResults = [];
164
+ for (let i = 0; i < this.children.length; i++) {
165
+ childResults.push(this.children[i].mount(this));
166
+ }
167
+ const childElements = flattenNodes(childResults);
159
168
  // Queue onMount callbacks after children are mounted
160
169
  // This ensures refs and other child lifecycle hooks run before parent onMount
161
170
  instance.onMounts.forEach((cb) => {
@@ -1,14 +1,16 @@
1
- import { AbstractVNode } from "./AbstractVNode";
2
- import { Props, VNode } from "./types";
1
+ import { AbstractVNode, PatchOperation } from "./AbstractVNode";
2
+ import { Props, VNode, VFlags } from "./types";
3
3
  export declare class ElementVNode extends AbstractVNode {
4
4
  tag: string;
5
5
  props: Props;
6
6
  children: VNode[];
7
7
  key?: string;
8
+ flags: VFlags;
8
9
  private ref?;
9
10
  private eventListeners?;
10
11
  constructor(tag: string, { ref, ...props }: Props, children: VNode[], key?: string);
11
- rerender(): void;
12
+ private computeFlags;
13
+ rerender(operations?: PatchOperation[]): void;
12
14
  mount(parent?: VNode): Node;
13
15
  /**
14
16
  * An ELEMENT patch goes through three operations
@@ -19,12 +21,7 @@ export declare class ElementVNode extends AbstractVNode {
19
21
  unmount(): void;
20
22
  private setProp;
21
23
  private patchProps;
24
+ private patchStyle;
22
25
  private addEventListener;
23
- /**
24
- * Intelligently sync DOM to match children VNode order.
25
- * Only performs DOM operations when elements are out of position.
26
- * This is used by both patch() and rerender() to efficiently update children.
27
- */
28
- private syncDOMChildren;
29
26
  }
30
27
  //# sourceMappingURL=ElementVNode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ElementVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ElementVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUvC,qBAAa,YAAa,SAAQ,aAAa;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,GAAG,CAAC,CAA0D;IACtE,OAAO,CAAC,cAAc,CAAC,CAA6B;gBAElD,GAAG,EAAE,MAAM,EACX,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,EACxB,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IASd,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;IAkC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY;IAY3B,OAAO;IAYP,OAAO,CAAC,OAAO,CAoCb;IACF,OAAO,CAAC,UAAU;IAGlB,OAAO,CAAC,gBAAgB;IAgBxB;;;;OAIG;IACH,OAAO,CAAC,eAAe;CAyBxB"}
1
+ {"version":3,"file":"ElementVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/ElementVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAU/C,qBAAa,YAAa,SAAQ,aAAa;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,CAA0D;IACtE,OAAO,CAAC,cAAc,CAAC,CAA6B;gBAElD,GAAG,EAAE,MAAM,EACX,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,EACxB,QAAQ,EAAE,KAAK,EAAE,EACjB,GAAG,CAAC,EAAE,MAAM;IAad,OAAO,CAAC,YAAY;IAwBpB,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAO7C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI;IAkC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY;IAwB3B,OAAO;IAYP,OAAO,CAAC,OAAO,CAoCb;IACF,OAAO,CAAC,UAAU;IAsClB,OAAO,CAAC,UAAU;IAoClB,OAAO,CAAC,gBAAgB;CAgBzB"}
@@ -7,6 +7,7 @@ export class ElementVNode extends AbstractVNode {
7
7
  props;
8
8
  children;
9
9
  key;
10
+ flags;
10
11
  ref;
11
12
  eventListeners;
12
13
  constructor(tag, { ref, ...props }, children, key) {
@@ -16,9 +17,39 @@ export class ElementVNode extends AbstractVNode {
16
17
  this.children = children;
17
18
  this.key = key;
18
19
  this.ref = ref;
20
+ // Pre-compute flags for fast-path checks during patching
21
+ this.flags = this.computeFlags(props);
19
22
  }
20
- rerender() {
21
- this.syncDOMChildren();
23
+ computeFlags(props) {
24
+ let flags = 0 /* VFlags.None */;
25
+ const propKeys = Object.keys(props);
26
+ if (propKeys.length > 0) {
27
+ flags |= 1 /* VFlags.HasProps */;
28
+ for (let i = 0; i < propKeys.length; i++) {
29
+ const key = propKeys[i];
30
+ if (key === "class" || key === "className") {
31
+ flags |= 2 /* VFlags.HasClass */;
32
+ }
33
+ else if (key === "style") {
34
+ flags |= 4 /* VFlags.HasStyle */;
35
+ }
36
+ else if (isEventProp(key)) {
37
+ flags |= 8 /* VFlags.HasEvents */;
38
+ }
39
+ else if (key.startsWith("data-") || key.startsWith("aria-")) {
40
+ flags |= 16 /* VFlags.HasDataAttrs */;
41
+ }
42
+ }
43
+ }
44
+ return flags;
45
+ }
46
+ rerender(operations) {
47
+ if (operations) {
48
+ this.applyPatchOperations(this.getHTMLElement(), operations);
49
+ }
50
+ else {
51
+ this.syncDOMChildren();
52
+ }
22
53
  }
23
54
  mount(parent) {
24
55
  this.parent = parent;
@@ -55,13 +86,23 @@ export class ElementVNode extends AbstractVNode {
55
86
  * - Patch the children
56
87
  */
57
88
  patch(newNode) {
58
- this.patchProps(newNode.props);
89
+ // Save old flags before updating
90
+ const oldFlags = this.flags;
91
+ // Only patch props if either old or new node has props
92
+ if ((oldFlags | newNode.flags) & 1 /* VFlags.HasProps */) {
93
+ this.patchProps(newNode.props, oldFlags, newNode.flags);
94
+ }
95
+ // Update flags and props after patching
96
+ this.flags = newNode.flags;
59
97
  this.props = newNode.props;
60
- const { children, hasChangedStructure } = this.patchChildren(newNode.children);
98
+ const { children, hasChangedStructure, operations } = this.patchChildren(newNode.children);
61
99
  this.children = children;
62
100
  if (hasChangedStructure) {
63
101
  this.syncDOMChildren();
64
102
  }
103
+ else {
104
+ this.applyPatchOperations(this.getHTMLElement(), operations);
105
+ }
65
106
  }
66
107
  unmount() {
67
108
  this.children.forEach((child) => child.unmount());
@@ -102,8 +143,64 @@ export class ElementVNode extends AbstractVNode {
102
143
  }
103
144
  setElementProp(elm, prop, value);
104
145
  };
105
- patchProps(newProps) {
106
- diffObjectKeys(this.props, newProps, this.setProp);
146
+ patchProps(newProps, oldFlags, newFlags) {
147
+ // Early bailout for reference equality
148
+ if (this.props === newProps) {
149
+ return;
150
+ }
151
+ const oldProps = this.props;
152
+ const elm = this.getHTMLElement();
153
+ // Handle class separately for efficiency (check if either old or new has class)
154
+ if ((oldFlags | newFlags) & 2 /* VFlags.HasClass */) {
155
+ const oldClass = oldProps.class ?? oldProps.className;
156
+ const newClass = newProps.class ?? newProps.className;
157
+ if (oldClass !== newClass) {
158
+ setElementClass(elm, newClass);
159
+ }
160
+ }
161
+ // Handle style separately with per-property diffing (check if either old or new has style)
162
+ if ((oldFlags | newFlags) & 4 /* VFlags.HasStyle */) {
163
+ this.patchStyle(oldProps.style, newProps.style);
164
+ }
165
+ // Handle regular props (excluding class, className, style, children)
166
+ diffObjectKeys(oldProps, newProps, (key, value, oldValue) => {
167
+ // Skip props we've already handled
168
+ if (key === "class" ||
169
+ key === "className" ||
170
+ key === "style" ||
171
+ key === "children") {
172
+ return;
173
+ }
174
+ this.setProp(key, value);
175
+ });
176
+ }
177
+ patchStyle(oldStyle, newStyle) {
178
+ // Early bailout for reference equality
179
+ if (oldStyle === newStyle) {
180
+ return;
181
+ }
182
+ const elm = this.getHTMLElement();
183
+ // If either is a string, fall back to full replacement
184
+ if (typeof oldStyle === "string" || typeof newStyle === "string") {
185
+ setElementStyle(elm, newStyle);
186
+ return;
187
+ }
188
+ // Per-property style diffing for objects
189
+ const os = oldStyle || {};
190
+ const ns = newStyle || {};
191
+ // Remove old styles not in new
192
+ for (const key in os) {
193
+ if (!(key in ns)) {
194
+ elm.style[key] = "";
195
+ }
196
+ }
197
+ // Set new/changed styles
198
+ for (const key in ns) {
199
+ const newVal = ns[key];
200
+ if (newVal !== os[key]) {
201
+ elm.style[key] = newVal;
202
+ }
203
+ }
107
204
  }
108
205
  addEventListener(type, cb) {
109
206
  if (!this.eventListeners) {
@@ -120,32 +217,4 @@ export class ElementVNode extends AbstractVNode {
120
217
  delete this.eventListeners[type];
121
218
  }
122
219
  }
123
- /**
124
- * Intelligently sync DOM to match children VNode order.
125
- * Only performs DOM operations when elements are out of position.
126
- * This is used by both patch() and rerender() to efficiently update children.
127
- */
128
- syncDOMChildren() {
129
- const elm = this.elm;
130
- let currentDomChild = elm.firstChild;
131
- for (const child of this.children) {
132
- const childNodes = child.getElements();
133
- for (const node of childNodes) {
134
- if (currentDomChild === node) {
135
- // Already in correct position, advance pointer
136
- currentDomChild = currentDomChild.nextSibling;
137
- }
138
- else {
139
- // Insert (or move if it exists elsewhere in DOM)
140
- elm.insertBefore(node, currentDomChild);
141
- }
142
- }
143
- }
144
- // Remove any leftover nodes (shouldn't happen if unmount works correctly)
145
- while (currentDomChild) {
146
- const next = currentDomChild.nextSibling;
147
- elm.removeChild(currentDomChild);
148
- currentDomChild = next;
149
- }
150
- }
151
220
  }
@@ -1,4 +1,4 @@
1
- import { AbstractVNode } from "./AbstractVNode";
1
+ import { AbstractVNode, PatchOperation } from "./AbstractVNode";
2
2
  import { VNode } from "./types";
3
3
  export declare const Fragment: unique symbol;
4
4
  export declare class FragmentVNode extends AbstractVNode {
@@ -6,7 +6,7 @@ export declare class FragmentVNode extends AbstractVNode {
6
6
  key?: string;
7
7
  constructor(children: VNode[], key?: string);
8
8
  mount(parent?: VNode): Node[];
9
- rerender(): void;
9
+ rerender(operations?: PatchOperation[]): void;
10
10
  patch(newNode: FragmentVNode): void;
11
11
  unmount(): void;
12
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FragmentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/FragmentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKhD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAE3C,qBAAa,aAAc,SAAQ,aAAa;IAC9C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;gBAED,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM;IAK3C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAW7B,QAAQ,IAAI,IAAI;IAGhB,KAAK,CAAC,OAAO,EAAE,aAAa;IAS5B,OAAO;CAMR"}
1
+ {"version":3,"file":"FragmentVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/FragmentVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKhE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,eAAO,MAAM,QAAQ,eAAqB,CAAC;AAE3C,qBAAa,aAAc,SAAQ,aAAa;IAC9C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;gBAED,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM;IAK3C,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE;IAgB7B,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAG7C,KAAK,CAAC,OAAO,EAAE,aAAa;IAiB5B,OAAO;CAMR"}
@@ -1,5 +1,6 @@
1
1
  import { AbstractVNode } from "./AbstractVNode";
2
2
  import { RootVNode } from "./RootVNode";
3
+ import { flattenNodes } from "./dom-utils";
3
4
  export const Fragment = Symbol("Fragment");
4
5
  export class FragmentVNode extends AbstractVNode {
5
6
  children;
@@ -17,17 +18,27 @@ export class FragmentVNode extends AbstractVNode {
17
18
  else {
18
19
  this.root = parent?.root;
19
20
  }
20
- return this.children.map((child) => child.mount(this)).flat();
21
+ // Optimized: avoid intermediate arrays from map+flat
22
+ const childResults = [];
23
+ for (let i = 0; i < this.children.length; i++) {
24
+ childResults.push(this.children[i].mount(this));
25
+ }
26
+ return flattenNodes(childResults);
21
27
  }
22
- rerender() {
23
- this.parent?.rerender();
28
+ rerender(operations) {
29
+ this.parent?.rerender(operations);
24
30
  }
25
31
  patch(newNode) {
26
- const { children, hasChangedStructure } = this.patchChildren(newNode.children);
32
+ const { children, hasChangedStructure, operations } = this.patchChildren(newNode.children);
27
33
  this.children = children;
28
- if (hasChangedStructure) {
29
- this.rerender();
30
- }
34
+ // So we can safely pass remove/replace operations up to the parent, but add
35
+ // is very tricky as parent has potentially other children as well. This can be
36
+ // handled with some additional detection, changing it to insertBefore. This can be
37
+ // done by passing this vnode up to the parent
38
+ this.rerender(hasChangedStructure ||
39
+ operations.some((operation) => operation.type === "add")
40
+ ? undefined
41
+ : operations);
31
42
  }
32
43
  unmount() {
33
44
  this.children.forEach((child) => child.unmount());
@@ -1,4 +1,4 @@
1
- import { AbstractVNode } from "./AbstractVNode";
1
+ import { AbstractVNode, PatchOperation } from "./AbstractVNode";
2
2
  import { VNode } from "./types";
3
3
  import { ComponentInstance } from "./ComponentVNode";
4
4
  export declare let currentRoot: RootVNode | undefined;
@@ -19,7 +19,7 @@ export declare class RootVNode extends AbstractVNode {
19
19
  clearCurrent(): void;
20
20
  mount(): Node | Node[];
21
21
  patch(): void;
22
- rerender(): void;
22
+ rerender(operations?: PatchOperation[]): void;
23
23
  unmount(): void;
24
24
  }
25
25
  //# sourceMappingURL=RootVNode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RootVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/RootVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,eAAO,IAAI,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC;AAE9C,qBAAa,SAAU,SAAQ,aAAa;IAC1C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,cAAc,EAAE,iBAAiB,EAAE,CAAM;IACzC,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,gBAAgB,CAAyB;gBAErC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW;IAMnD,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI;IAIzB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI;IAG3B,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI;IAa5B,cAAc;IAOd,aAAa,CAAC,QAAQ,EAAE,iBAAiB;IAIzC,YAAY;IAIZ,YAAY;IAIZ,YAAY;IAMZ,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE;IAGtB,KAAK,IAAI,IAAI;IACb,QAAQ,IAAI,IAAI;IAShB,OAAO,IAAI,IAAI;CAChB"}
1
+ {"version":3,"file":"RootVNode.d.ts","sourceRoot":"","sources":["../../src/vdom/RootVNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAKrD,eAAO,IAAI,WAAW,EAAE,SAAS,GAAG,SAAS,CAAC;AAE9C,qBAAa,SAAU,SAAQ,aAAa;IAC1C,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,cAAc,EAAE,iBAAiB,EAAE,CAAM;IACzC,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,gBAAgB,CAAyB;gBAErC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW;IAMnD,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI;IAIzB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI;IAG3B,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI;IAa5B,cAAc;IAOd,aAAa,CAAC,QAAQ,EAAE,iBAAiB;IAIzC,YAAY;IAIZ,YAAY;IAIZ,YAAY;IAMZ,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE;IAStB,KAAK,IAAI,IAAI;IACb,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,GAAG,IAAI;IAS7C,OAAO,IAAI,IAAI;CAChB"}