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.
- package/dist/vdom/AbstractVNode.d.ts +20 -1
- package/dist/vdom/AbstractVNode.d.ts.map +1 -1
- package/dist/vdom/AbstractVNode.js +151 -12
- package/dist/vdom/ComponentVNode.d.ts +2 -2
- package/dist/vdom/ComponentVNode.d.ts.map +1 -1
- package/dist/vdom/ComponentVNode.js +17 -8
- package/dist/vdom/ElementVNode.d.ts +6 -9
- package/dist/vdom/ElementVNode.d.ts.map +1 -1
- package/dist/vdom/ElementVNode.js +103 -34
- package/dist/vdom/FragmentVNode.d.ts +2 -2
- package/dist/vdom/FragmentVNode.d.ts.map +1 -1
- package/dist/vdom/FragmentVNode.js +18 -7
- package/dist/vdom/RootVNode.d.ts +2 -2
- package/dist/vdom/RootVNode.d.ts.map +1 -1
- package/dist/vdom/RootVNode.js +15 -6
- package/dist/vdom/dom-utils.d.ts +6 -1
- package/dist/vdom/dom-utils.d.ts.map +1 -1
- package/dist/vdom/dom-utils.js +20 -1
- package/dist/vdom/types.d.ts +12 -0
- package/dist/vdom/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/component.d.ts +0 -38
- package/dist/component.d.ts.map +0 -1
- package/dist/component.js +0 -130
- package/dist/context.d.ts +0 -5
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -29
- package/dist/createAsync.test.d.ts +0 -2
- package/dist/createAsync.test.d.ts.map +0 -1
- package/dist/createAsync.test.js +0 -110
- package/dist/createAsyncState.d.ts +0 -16
- package/dist/createAsyncState.d.ts.map +0 -1
- package/dist/createAsyncState.js +0 -24
- package/dist/createContext.test.d.ts +0 -2
- package/dist/createContext.test.d.ts.map +0 -1
- package/dist/createContext.test.js +0 -136
- package/dist/createMutation.test.d.ts +0 -2
- package/dist/createMutation.test.d.ts.map +0 -1
- package/dist/createMutation.test.js +0 -168
- package/dist/createQuery.test.d.ts +0 -2
- package/dist/createQuery.test.d.ts.map +0 -1
- package/dist/createQuery.test.js +0 -156
- package/dist/createRef.test.d.ts +0 -2
- package/dist/createRef.test.d.ts.map +0 -1
- package/dist/createRef.test.js +0 -80
- package/dist/createState.test.d.ts +0 -2
- package/dist/createState.test.d.ts.map +0 -1
- package/dist/createState.test.js +0 -111
- package/dist/createView.test.d.ts +0 -2
- package/dist/createView.test.d.ts.map +0 -1
- package/dist/createView.test.js +0 -203
- package/dist/error.test.d.ts +0 -2
- package/dist/error.test.d.ts.map +0 -1
- package/dist/error.test.js +0 -144
- package/dist/integration.test.d.ts +0 -2
- package/dist/integration.test.d.ts.map +0 -1
- package/dist/integration.test.js +0 -155
- package/dist/jsx.d.ts.map +0 -1
- package/dist/jsx.js +0 -42
- package/dist/observation.test.d.ts +0 -2
- package/dist/observation.test.d.ts.map +0 -1
- package/dist/observation.test.js +0 -113
- package/dist/render-test.d.ts +0 -2
- package/dist/render-test.d.ts.map +0 -1
- package/dist/render-test.js +0 -21
- package/dist/render.d.ts +0 -7
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js +0 -77
- package/dist/suspense.d.ts +0 -25
- package/dist/suspense.d.ts.map +0 -1
- package/dist/suspense.js +0 -97
- package/dist/tests/class.test.d.ts +0 -2
- package/dist/tests/class.test.d.ts.map +0 -1
- package/dist/tests/class.test.js +0 -185
- package/dist/tests/complex-rendering.test.d.ts +0 -2
- package/dist/tests/complex-rendering.test.d.ts.map +0 -1
- package/dist/tests/complex-rendering.test.js +0 -400
- package/dist/tests/component.cleanup.test.d.ts +0 -2
- package/dist/tests/component.cleanup.test.d.ts.map +0 -1
- package/dist/tests/component.cleanup.test.js +0 -325
- package/dist/tests/component.counter.test.d.ts +0 -2
- package/dist/tests/component.counter.test.d.ts.map +0 -1
- package/dist/tests/component.counter.test.js +0 -124
- package/dist/tests/component.interaction.test.d.ts +0 -2
- package/dist/tests/component.interaction.test.d.ts.map +0 -1
- package/dist/tests/component.interaction.test.js +0 -73
- package/dist/tests/component.props.test.d.ts +0 -2
- package/dist/tests/component.props.test.d.ts.map +0 -1
- package/dist/tests/component.props.test.js +0 -334
- package/dist/tests/component.return-types.test.d.ts +0 -2
- package/dist/tests/component.return-types.test.d.ts.map +0 -1
- package/dist/tests/component.return-types.test.js +0 -357
- package/dist/tests/component.state.test.d.ts +0 -2
- package/dist/tests/component.state.test.d.ts.map +0 -1
- package/dist/tests/component.state.test.js +0 -135
- package/dist/tests/component.test.d.ts +0 -2
- package/dist/tests/component.test.d.ts.map +0 -1
- package/dist/tests/component.test.js +0 -63
- package/dist/tests/createAsync.test.d.ts +0 -2
- package/dist/tests/createAsync.test.d.ts.map +0 -1
- package/dist/tests/createAsync.test.js +0 -110
- package/dist/tests/createContext.test.d.ts +0 -2
- package/dist/tests/createContext.test.d.ts.map +0 -1
- package/dist/tests/createContext.test.js +0 -141
- package/dist/tests/createMutation.test.d.ts +0 -2
- package/dist/tests/createMutation.test.d.ts.map +0 -1
- package/dist/tests/createMutation.test.js +0 -168
- package/dist/tests/createQuery.test.d.ts +0 -2
- package/dist/tests/createQuery.test.d.ts.map +0 -1
- package/dist/tests/createQuery.test.js +0 -156
- package/dist/tests/createRef.test.d.ts +0 -2
- package/dist/tests/createRef.test.d.ts.map +0 -1
- package/dist/tests/createRef.test.js +0 -84
- package/dist/tests/createState.test.d.ts +0 -2
- package/dist/tests/createState.test.d.ts.map +0 -1
- package/dist/tests/createState.test.js +0 -103
- package/dist/tests/createView.test.d.ts +0 -2
- package/dist/tests/createView.test.d.ts.map +0 -1
- package/dist/tests/createView.test.js +0 -203
- package/dist/tests/edge-cases.test.d.ts +0 -2
- package/dist/tests/edge-cases.test.d.ts.map +0 -1
- package/dist/tests/edge-cases.test.js +0 -637
- package/dist/tests/error-no-boundary.test.d.ts +0 -2
- package/dist/tests/error-no-boundary.test.d.ts.map +0 -1
- package/dist/tests/error-no-boundary.test.js +0 -174
- package/dist/tests/error.test.d.ts +0 -2
- package/dist/tests/error.test.d.ts.map +0 -1
- package/dist/tests/error.test.js +0 -199
- package/dist/tests/fragment.test.d.ts +0 -2
- package/dist/tests/fragment.test.d.ts.map +0 -1
- package/dist/tests/fragment.test.js +0 -618
- package/dist/tests/integration.test.d.ts +0 -2
- package/dist/tests/integration.test.d.ts.map +0 -1
- package/dist/tests/integration.test.js +0 -192
- package/dist/tests/keys.test.d.ts +0 -2
- package/dist/tests/keys.test.d.ts.map +0 -1
- package/dist/tests/keys.test.js +0 -293
- package/dist/tests/mount.test.d.ts +0 -2
- package/dist/tests/mount.test.d.ts.map +0 -1
- package/dist/tests/mount.test.js +0 -91
- package/dist/tests/observation.test.d.ts +0 -2
- package/dist/tests/observation.test.d.ts.map +0 -1
- package/dist/tests/observation.test.js +0 -113
- package/dist/tests/patch.test.d.ts +0 -2
- package/dist/tests/patch.test.d.ts.map +0 -1
- package/dist/tests/patch.test.js +0 -498
- package/dist/tests/patchChildren.test.d.ts +0 -2
- package/dist/tests/patchChildren.test.d.ts.map +0 -1
- package/dist/tests/patchChildren.test.js +0 -405
- package/dist/tests/primitives.test.d.ts +0 -2
- package/dist/tests/primitives.test.d.ts.map +0 -1
- package/dist/tests/primitives.test.js +0 -132
- package/dist/vdom/class.test.d.ts +0 -2
- package/dist/vdom/class.test.d.ts.map +0 -1
- package/dist/vdom/class.test.js +0 -143
- package/dist/vdom/complex-rendering.test.d.ts +0 -2
- package/dist/vdom/complex-rendering.test.d.ts.map +0 -1
- package/dist/vdom/complex-rendering.test.js +0 -400
- package/dist/vdom/component.cleanup.test.d.ts +0 -2
- package/dist/vdom/component.cleanup.test.d.ts.map +0 -1
- package/dist/vdom/component.cleanup.test.js +0 -323
- package/dist/vdom/component.counter.test.d.ts +0 -2
- package/dist/vdom/component.counter.test.d.ts.map +0 -1
- package/dist/vdom/component.counter.test.js +0 -124
- package/dist/vdom/component.interaction.test.d.ts +0 -2
- package/dist/vdom/component.interaction.test.d.ts.map +0 -1
- package/dist/vdom/component.interaction.test.js +0 -73
- package/dist/vdom/component.props.test.d.ts +0 -2
- package/dist/vdom/component.props.test.d.ts.map +0 -1
- package/dist/vdom/component.props.test.js +0 -88
- package/dist/vdom/component.return-types.test.d.ts +0 -2
- package/dist/vdom/component.return-types.test.d.ts.map +0 -1
- package/dist/vdom/component.return-types.test.js +0 -357
- package/dist/vdom/component.state.test.d.ts +0 -2
- package/dist/vdom/component.state.test.d.ts.map +0 -1
- package/dist/vdom/component.state.test.js +0 -129
- package/dist/vdom/component.test.d.ts +0 -2
- package/dist/vdom/component.test.d.ts.map +0 -1
- package/dist/vdom/component.test.js +0 -63
- package/dist/vdom/edge-cases.test.d.ts +0 -2
- package/dist/vdom/edge-cases.test.d.ts.map +0 -1
- package/dist/vdom/edge-cases.test.js +0 -637
- package/dist/vdom/fragment.test.d.ts +0 -2
- package/dist/vdom/fragment.test.d.ts.map +0 -1
- package/dist/vdom/fragment.test.js +0 -618
- package/dist/vdom/keys.test.d.ts +0 -2
- package/dist/vdom/keys.test.d.ts.map +0 -1
- package/dist/vdom/keys.test.js +0 -293
- package/dist/vdom/mount.test.d.ts +0 -2
- package/dist/vdom/mount.test.d.ts.map +0 -1
- package/dist/vdom/mount.test.js +0 -91
- package/dist/vdom/patch.test.d.ts +0 -2
- package/dist/vdom/patch.test.d.ts.map +0 -1
- package/dist/vdom/patch.test.js +0 -498
- package/dist/vdom/patchChildren.test.d.ts +0 -2
- package/dist/vdom/patchChildren.test.d.ts.map +0 -1
- package/dist/vdom/patchChildren.test.js +0 -392
- package/dist/vdom/primitives.test.d.ts +0 -2
- package/dist/vdom/primitives.test.d.ts.map +0 -1
- 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":"
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
|
99
|
-
const { children, hasChangedStructure } = this.patchChildren(newChildren);
|
|
99
|
+
const { children, hasChangedStructure, operations } = this.patchChildren(newChildren);
|
|
100
100
|
this.children = children;
|
|
101
|
-
if
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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());
|
package/dist/vdom/RootVNode.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|