rask-ui 0.3.4 → 0.4.1
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/README.md +266 -96
- package/dist/batch.d.ts +7 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +107 -0
- package/dist/component.d.ts +9 -1
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +42 -12
- package/dist/createComputed.d.ts +4 -0
- package/dist/createComputed.d.ts.map +1 -0
- package/dist/createComputed.js +41 -0
- package/dist/createEffect.d.ts +2 -0
- package/dist/createEffect.d.ts.map +1 -0
- package/dist/createEffect.js +25 -0
- package/dist/createState.d.ts +1 -0
- package/dist/createState.d.ts.map +1 -1
- package/dist/createState.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/observation.d.ts +82 -6
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +171 -21
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +195 -0
- package/dist/scheduler.d.ts +4 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +107 -0
- package/package.json +1 -1
- package/dist/createRef.d.ts +0 -5
- package/dist/createRef.d.ts.map +0 -1
- package/dist/createRef.js +0 -7
- package/dist/test-setup.d.ts +0 -16
- package/dist/test-setup.d.ts.map +0 -1
- package/dist/test-setup.js +0 -40
- package/dist/vdom/AbstractVNode.d.ts +0 -44
- package/dist/vdom/AbstractVNode.d.ts.map +0 -1
- package/dist/vdom/AbstractVNode.js +0 -256
- package/dist/vdom/ComponentVNode.d.ts +0 -48
- package/dist/vdom/ComponentVNode.d.ts.map +0 -1
- package/dist/vdom/ComponentVNode.js +0 -221
- package/dist/vdom/ElementVNode.d.ts +0 -27
- package/dist/vdom/ElementVNode.d.ts.map +0 -1
- package/dist/vdom/ElementVNode.js +0 -220
- package/dist/vdom/FragmentVNode.d.ts +0 -13
- package/dist/vdom/FragmentVNode.d.ts.map +0 -1
- package/dist/vdom/FragmentVNode.js +0 -49
- package/dist/vdom/RootVNode.d.ts +0 -25
- package/dist/vdom/RootVNode.d.ts.map +0 -1
- package/dist/vdom/RootVNode.js +0 -79
- package/dist/vdom/TextVNode.d.ts +0 -11
- package/dist/vdom/TextVNode.d.ts.map +0 -1
- package/dist/vdom/TextVNode.js +0 -35
- package/dist/vdom/dom-utils.d.ts +0 -14
- package/dist/vdom/dom-utils.d.ts.map +0 -1
- package/dist/vdom/dom-utils.js +0 -103
- package/dist/vdom/index.d.ts +0 -10
- package/dist/vdom/index.d.ts.map +0 -1
- package/dist/vdom/index.js +0 -26
- package/dist/vdom/types.d.ts +0 -20
- package/dist/vdom/types.d.ts.map +0 -1
- package/dist/vdom/types.js +0 -1
- package/dist/vdom/utils.d.ts +0 -6
- package/dist/vdom/utils.d.ts.map +0 -1
- package/dist/vdom/utils.js +0 -63
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { elementsToFragment } from "./dom-utils";
|
|
2
|
-
export class AbstractVNode {
|
|
3
|
-
key;
|
|
4
|
-
parent;
|
|
5
|
-
root;
|
|
6
|
-
elm;
|
|
7
|
-
children;
|
|
8
|
-
getHTMLElement() {
|
|
9
|
-
if (!this.elm || !(this.elm instanceof HTMLElement)) {
|
|
10
|
-
throw new Error("This VNode does not have an HTMLElement");
|
|
11
|
-
}
|
|
12
|
-
return this.elm;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* A VNode can represent multiple elements (fragment of component)
|
|
16
|
-
*/
|
|
17
|
-
getElements() {
|
|
18
|
-
if (this.elm) {
|
|
19
|
-
return [this.elm];
|
|
20
|
-
}
|
|
21
|
-
if (!this.children) {
|
|
22
|
-
throw new Error("This VNode has no element or children");
|
|
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;
|
|
33
|
-
}
|
|
34
|
-
getParentElement() {
|
|
35
|
-
let parent = this.parent;
|
|
36
|
-
// This VNode might not have an element, but relies
|
|
37
|
-
// on a parent for it. So we make sure that we get
|
|
38
|
-
// the actual parent of the element related to this VNode
|
|
39
|
-
while (parent) {
|
|
40
|
-
if (parent.elm instanceof HTMLElement) {
|
|
41
|
-
// This will always be an HTMLElement as text nodes has no children
|
|
42
|
-
return parent.elm;
|
|
43
|
-
}
|
|
44
|
-
parent = parent.parent;
|
|
45
|
-
}
|
|
46
|
-
throw new Error("There is no parent element for this VNode");
|
|
47
|
-
}
|
|
48
|
-
canPatch(oldNode, newNode) {
|
|
49
|
-
// Must be same constructor type
|
|
50
|
-
if (oldNode.constructor !== newNode.constructor) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
// For ElementVNodes, must have same tag
|
|
54
|
-
if ("tag" in oldNode && "tag" in newNode) {
|
|
55
|
-
return oldNode.tag === newNode.tag;
|
|
56
|
-
}
|
|
57
|
-
// For ComponentVNodes, must have same component function
|
|
58
|
-
if ("component" in oldNode && "component" in newNode) {
|
|
59
|
-
return oldNode.component === newNode.component;
|
|
60
|
-
}
|
|
61
|
-
// TextVNodes and FragmentVNodes can always patch
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
patchChildren(newChildren) {
|
|
65
|
-
const prevChildren = this.children;
|
|
66
|
-
if (newChildren.length === 0 && prevChildren.length === 0) {
|
|
67
|
-
return { children: [], hasChangedStructure: false };
|
|
68
|
-
}
|
|
69
|
-
// When there are only new children, we just mount them
|
|
70
|
-
if (prevChildren.length === 0) {
|
|
71
|
-
newChildren.forEach((child) => child.mount(this));
|
|
72
|
-
return {
|
|
73
|
-
children: newChildren,
|
|
74
|
-
hasChangedStructure: true,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
// If we want to remove all children, we just unmount the previous ones
|
|
78
|
-
if (!newChildren.length && prevChildren.length) {
|
|
79
|
-
prevChildren.forEach((child) => child.unmount());
|
|
80
|
-
return { children: [], hasChangedStructure: true, operations: [] };
|
|
81
|
-
}
|
|
82
|
-
const oldKeys = {};
|
|
83
|
-
prevChildren.forEach((prevChild, index) => {
|
|
84
|
-
oldKeys[prevChild.key || index] = {
|
|
85
|
-
vnode: prevChild,
|
|
86
|
-
index,
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
// Build result array in the NEW order
|
|
90
|
-
const result = [];
|
|
91
|
-
const operations = [];
|
|
92
|
-
// Track indices of reused nodes in their original order
|
|
93
|
-
const reusedOldIndices = [];
|
|
94
|
-
// Track which result positions contain new nodes (not reused/replaced)
|
|
95
|
-
const isNewNode = [];
|
|
96
|
-
let forceStructuralChange = false;
|
|
97
|
-
newChildren.forEach((newChild, index) => {
|
|
98
|
-
const key = newChild.key || index;
|
|
99
|
-
const prevChild = oldKeys[key];
|
|
100
|
-
if (!prevChild) {
|
|
101
|
-
// New child - mount and add to result
|
|
102
|
-
const elm = newChild.mount(this);
|
|
103
|
-
result.push(newChild);
|
|
104
|
-
isNewNode.push(true);
|
|
105
|
-
operations.push({ type: "add", elm });
|
|
106
|
-
}
|
|
107
|
-
else if (prevChild?.vnode === newChild) {
|
|
108
|
-
// Same instance - no patching needed, just reuse
|
|
109
|
-
result.push(prevChild.vnode);
|
|
110
|
-
isNewNode.push(false);
|
|
111
|
-
reusedOldIndices.push(prevChild.index);
|
|
112
|
-
delete oldKeys[key];
|
|
113
|
-
}
|
|
114
|
-
else if (this.canPatch(prevChild.vnode, newChild)) {
|
|
115
|
-
// Compatible types - patch and reuse old VNode
|
|
116
|
-
prevChild.vnode.patch(newChild);
|
|
117
|
-
result.push(prevChild.vnode);
|
|
118
|
-
isNewNode.push(false);
|
|
119
|
-
reusedOldIndices.push(prevChild.index);
|
|
120
|
-
delete oldKeys[key];
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
// Incompatible types - replace completely
|
|
124
|
-
const newElm = newChild.mount(this);
|
|
125
|
-
prevChild.vnode.unmount();
|
|
126
|
-
result.push(newChild);
|
|
127
|
-
isNewNode.push(false); // Replacement, not an insertion
|
|
128
|
-
delete oldKeys[key];
|
|
129
|
-
const oldElm = prevChild.vnode.getElements();
|
|
130
|
-
// We need to fall back to structural change when the old node does
|
|
131
|
-
// not have any elements. This can happen when a component returns null,
|
|
132
|
-
// throws an error etc.
|
|
133
|
-
if (!oldElm.length) {
|
|
134
|
-
forceStructuralChange = true;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
operations.push({
|
|
138
|
-
type: "replace",
|
|
139
|
-
oldElm,
|
|
140
|
-
newElm,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
// Unmount any old children that weren't reused
|
|
145
|
-
for (const key in oldKeys) {
|
|
146
|
-
oldKeys[key].vnode.unmount();
|
|
147
|
-
operations.push({
|
|
148
|
-
type: "remove",
|
|
149
|
-
elm: oldKeys[key].vnode.getElements(),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// Detect structural changes:
|
|
153
|
-
// 1. Reordering: reused nodes are not in their original relative order
|
|
154
|
-
// 2. Insertion in middle: new nodes inserted before existing nodes
|
|
155
|
-
let hasReordering = false;
|
|
156
|
-
let hasInsertionInMiddle = false;
|
|
157
|
-
if (!forceStructuralChange) {
|
|
158
|
-
for (let i = 1; i < reusedOldIndices.length; i++) {
|
|
159
|
-
if (reusedOldIndices[i] < reusedOldIndices[i - 1]) {
|
|
160
|
-
hasReordering = true;
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (!hasReordering) {
|
|
166
|
-
// Find the last position that contains a reused/replaced node
|
|
167
|
-
let lastReusedResultIndex = -1;
|
|
168
|
-
for (let i = result.length - 1; i >= 0; i--) {
|
|
169
|
-
if (!isNewNode[i]) {
|
|
170
|
-
lastReusedResultIndex = i;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Check if any new nodes were inserted before the last reused/replaced node
|
|
175
|
-
if (lastReusedResultIndex >= 0) {
|
|
176
|
-
for (let i = 0; i < lastReusedResultIndex; i++) {
|
|
177
|
-
if (isNewNode[i]) {
|
|
178
|
-
hasInsertionInMiddle = true;
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const hasChangedStructure = forceStructuralChange || hasReordering || hasInsertionInMiddle;
|
|
185
|
-
if (hasChangedStructure) {
|
|
186
|
-
operations.length = 0;
|
|
187
|
-
}
|
|
188
|
-
return { children: result, hasChangedStructure, operations };
|
|
189
|
-
}
|
|
190
|
-
applyPatchOperations(target, operations) {
|
|
191
|
-
operations.forEach((operation) => {
|
|
192
|
-
switch (operation.type) {
|
|
193
|
-
case "add": {
|
|
194
|
-
target.appendChild(elementsToFragment(operation.elm));
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
case "remove": {
|
|
198
|
-
if (Array.isArray(operation.elm)) {
|
|
199
|
-
const range = new Range();
|
|
200
|
-
range.setStartBefore(operation.elm[0]);
|
|
201
|
-
range.setEndAfter(operation.elm[operation.elm.length - 1]);
|
|
202
|
-
range.deleteContents();
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
target.removeChild(operation.elm);
|
|
206
|
-
}
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case "replace": {
|
|
210
|
-
if (Array.isArray(operation.oldElm)) {
|
|
211
|
-
const range = new Range();
|
|
212
|
-
range.setStartBefore(operation.oldElm[0]);
|
|
213
|
-
range.setEndAfter(operation.oldElm[operation.oldElm.length - 1]);
|
|
214
|
-
range.deleteContents();
|
|
215
|
-
range.insertNode(elementsToFragment(operation.newElm));
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
target.replaceChild(elementsToFragment(operation.newElm), operation.oldElm);
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Intelligently sync DOM to match children VNode order.
|
|
227
|
-
* Only performs DOM operations when elements are out of position.
|
|
228
|
-
* This is used by both patch() and rerender() to efficiently update children.
|
|
229
|
-
*/
|
|
230
|
-
syncDOMChildren() {
|
|
231
|
-
if (!this.children) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const elm = this.elm;
|
|
235
|
-
let currentDomChild = elm.firstChild;
|
|
236
|
-
for (const child of this.children) {
|
|
237
|
-
const childNodes = child.getElements();
|
|
238
|
-
for (const node of childNodes) {
|
|
239
|
-
if (currentDomChild === node) {
|
|
240
|
-
// Already in correct position, advance pointer
|
|
241
|
-
currentDomChild = currentDomChild.nextSibling;
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
// Insert (or move if it exists elsewhere in DOM)
|
|
245
|
-
elm.insertBefore(node, currentDomChild);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// Remove any leftover nodes (shouldn't happen if unmount works correctly)
|
|
250
|
-
while (currentDomChild) {
|
|
251
|
-
const next = currentDomChild.nextSibling;
|
|
252
|
-
elm.removeChild(currentDomChild);
|
|
253
|
-
currentDomChild = next;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Observer } from "../observation";
|
|
2
|
-
import { AbstractVNode, PatchOperation } from "./AbstractVNode";
|
|
3
|
-
import { Props, VNode } from "./types";
|
|
4
|
-
export type ComponentChild = VNode | string | null | number | undefined | boolean;
|
|
5
|
-
export type ComponentChildren = ComponentChild | ComponentChild[];
|
|
6
|
-
/**
|
|
7
|
-
* Component function type. Components receive reactive props that should not be destructured.
|
|
8
|
-
*
|
|
9
|
-
* @warning **Do not destructure props!** Props are wrapped in a reactive proxy, and destructuring
|
|
10
|
-
* breaks reactivity. This is the same rule as Solid.js.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* // ❌ Bad - destructuring props loses reactivity
|
|
14
|
-
* function MyComponent({ count, name }) {
|
|
15
|
-
* return () => <div>{count} {name}</div>; // Won't update!
|
|
16
|
-
* }
|
|
17
|
-
*
|
|
18
|
-
* // ✅ Good - access props directly in render
|
|
19
|
-
* function MyComponent(props) {
|
|
20
|
-
* return () => <div>{props.count} {props.name}</div>; // Reactive!
|
|
21
|
-
* }
|
|
22
|
-
*/
|
|
23
|
-
export type Component<P extends Props> = ((props: P) => () => ComponentChildren) | (() => () => ComponentChildren);
|
|
24
|
-
export type ComponentInstance = {
|
|
25
|
-
parent?: VNode;
|
|
26
|
-
contexts: Map<object, unknown> | null;
|
|
27
|
-
onMounts: Array<() => void>;
|
|
28
|
-
onCleanups: Array<() => void>;
|
|
29
|
-
observer: Observer;
|
|
30
|
-
reactiveProps: object;
|
|
31
|
-
error: unknown;
|
|
32
|
-
notifyError(error: unknown): void;
|
|
33
|
-
};
|
|
34
|
-
export declare function getCurrentComponent(): ComponentInstance;
|
|
35
|
-
export declare function onMount(cb: () => void): void;
|
|
36
|
-
export declare function onCleanup(cb: () => void): void;
|
|
37
|
-
export declare class ComponentVNode extends AbstractVNode {
|
|
38
|
-
component: Component<any>;
|
|
39
|
-
props: Props;
|
|
40
|
-
children: VNode[];
|
|
41
|
-
instance?: ComponentInstance;
|
|
42
|
-
constructor(component: Component<any>, props: Props, children: VNode[], key?: string);
|
|
43
|
-
rerender(operations?: PatchOperation[]): void;
|
|
44
|
-
mount(parent?: VNode): Node[];
|
|
45
|
-
patch(newNode: ComponentVNode): void;
|
|
46
|
-
unmount(): void;
|
|
47
|
-
}
|
|
48
|
-
//# sourceMappingURL=ComponentVNode.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { getCurrentObserver, Observer, Signal } from "../observation";
|
|
2
|
-
import { AbstractVNode } from "./AbstractVNode";
|
|
3
|
-
import { FragmentVNode } from "./FragmentVNode";
|
|
4
|
-
import { RootVNode } from "./RootVNode";
|
|
5
|
-
import { normalizeChildren } from "./utils";
|
|
6
|
-
import { flattenNodes } from "./dom-utils";
|
|
7
|
-
import { currentRoot } from "./RootVNode";
|
|
8
|
-
export function getCurrentComponent() {
|
|
9
|
-
if (!currentRoot) {
|
|
10
|
-
throw new Error("No current root");
|
|
11
|
-
}
|
|
12
|
-
const currentComponent = currentRoot.componentStack[0];
|
|
13
|
-
if (!currentComponent) {
|
|
14
|
-
throw new Error("No current component");
|
|
15
|
-
}
|
|
16
|
-
return currentComponent;
|
|
17
|
-
}
|
|
18
|
-
export function onMount(cb) {
|
|
19
|
-
if (!currentRoot) {
|
|
20
|
-
throw new Error("Only use onCleanup in component setup");
|
|
21
|
-
}
|
|
22
|
-
const current = currentRoot.componentStack[0];
|
|
23
|
-
if (!current) {
|
|
24
|
-
throw new Error("Only use onCleanup in component setup");
|
|
25
|
-
}
|
|
26
|
-
current.onMounts.push(cb);
|
|
27
|
-
}
|
|
28
|
-
export function onCleanup(cb) {
|
|
29
|
-
if (!currentRoot) {
|
|
30
|
-
throw new Error("Only use onCleanup in component setup");
|
|
31
|
-
}
|
|
32
|
-
const current = currentRoot.componentStack[0];
|
|
33
|
-
if (!current) {
|
|
34
|
-
throw new Error("Only use onCleanup in component setup");
|
|
35
|
-
}
|
|
36
|
-
current.onCleanups.push(cb);
|
|
37
|
-
}
|
|
38
|
-
export class ComponentVNode extends AbstractVNode {
|
|
39
|
-
component;
|
|
40
|
-
props;
|
|
41
|
-
// These are the actual current children returned by component
|
|
42
|
-
children = [];
|
|
43
|
-
instance;
|
|
44
|
-
constructor(component, props, children, key) {
|
|
45
|
-
super();
|
|
46
|
-
this.component = component;
|
|
47
|
-
this.props = {
|
|
48
|
-
...props,
|
|
49
|
-
children,
|
|
50
|
-
};
|
|
51
|
-
this.children = [];
|
|
52
|
-
this.key = key;
|
|
53
|
-
}
|
|
54
|
-
rerender(operations) {
|
|
55
|
-
this.parent?.rerender(operations);
|
|
56
|
-
}
|
|
57
|
-
mount(parent) {
|
|
58
|
-
this.parent = parent;
|
|
59
|
-
if (parent instanceof RootVNode) {
|
|
60
|
-
this.root = parent;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
this.root = parent?.root;
|
|
64
|
-
}
|
|
65
|
-
let errorSignal;
|
|
66
|
-
let error;
|
|
67
|
-
const executeRender = () => {
|
|
68
|
-
const stopObserving = instance.observer.observe();
|
|
69
|
-
let renderResult = new FragmentVNode([]);
|
|
70
|
-
try {
|
|
71
|
-
renderResult = render();
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
instance.notifyError(error);
|
|
75
|
-
}
|
|
76
|
-
finally {
|
|
77
|
-
stopObserving();
|
|
78
|
-
}
|
|
79
|
-
return normalizeChildren(renderResult);
|
|
80
|
-
};
|
|
81
|
-
let isObserverQueued = false;
|
|
82
|
-
const instance = (this.instance = {
|
|
83
|
-
parent,
|
|
84
|
-
contexts: null,
|
|
85
|
-
onCleanups: [],
|
|
86
|
-
onMounts: [],
|
|
87
|
-
observer: new Observer(() => {
|
|
88
|
-
if (isObserverQueued) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
isObserverQueued = true;
|
|
92
|
-
this.root?.queueObserver(() => {
|
|
93
|
-
if (instance.observer.isDisposed) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
isObserverQueued = false;
|
|
97
|
-
this.root?.setAsCurrent();
|
|
98
|
-
const newChildren = executeRender();
|
|
99
|
-
const { children, hasChangedStructure, operations } = this.patchChildren(newChildren);
|
|
100
|
-
this.children = children;
|
|
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) {
|
|
105
|
-
this.parent?.rerender();
|
|
106
|
-
}
|
|
107
|
-
else if (operations?.length) {
|
|
108
|
-
this.parent?.rerender(operations);
|
|
109
|
-
}
|
|
110
|
-
this.root?.clearCurrent();
|
|
111
|
-
});
|
|
112
|
-
}),
|
|
113
|
-
reactiveProps: createReactiveProps(this.props),
|
|
114
|
-
get error() {
|
|
115
|
-
if (!errorSignal) {
|
|
116
|
-
errorSignal = new Signal();
|
|
117
|
-
}
|
|
118
|
-
const observer = getCurrentObserver();
|
|
119
|
-
if (observer) {
|
|
120
|
-
observer.subscribeSignal(errorSignal);
|
|
121
|
-
}
|
|
122
|
-
return error;
|
|
123
|
-
},
|
|
124
|
-
notifyError(childError) {
|
|
125
|
-
if (errorSignal) {
|
|
126
|
-
error = childError;
|
|
127
|
-
errorSignal.notify();
|
|
128
|
-
}
|
|
129
|
-
else if (instance.parent) {
|
|
130
|
-
let parent = instance.parent;
|
|
131
|
-
while (parent && !(parent instanceof ComponentVNode)) {
|
|
132
|
-
parent = parent?.parent;
|
|
133
|
-
}
|
|
134
|
-
// Only call notifyError if we found a ComponentVNode parent
|
|
135
|
-
if (parent instanceof ComponentVNode) {
|
|
136
|
-
parent.instance?.notifyError(childError);
|
|
137
|
-
}
|
|
138
|
-
// Otherwise there's no ErrorBoundary, so do nothing
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
throw childError;
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
this.root?.setAsCurrent();
|
|
146
|
-
this.root?.pushComponent(instance);
|
|
147
|
-
const render = this.component(instance.reactiveProps);
|
|
148
|
-
// Validate that render is a function
|
|
149
|
-
if (typeof render !== "function") {
|
|
150
|
-
const initError = new Error(`Component must return a render function, but got ${render instanceof AbstractVNode ? "JSX" : typeof render}. ` +
|
|
151
|
-
`Components should return a function that returns JSX: \`return () => <div>...</div>\``);
|
|
152
|
-
console.error("Error initializing component:", initError);
|
|
153
|
-
error = initError;
|
|
154
|
-
instance.notifyError(initError);
|
|
155
|
-
this.root?.popComponent();
|
|
156
|
-
this.root?.clearCurrent();
|
|
157
|
-
return [];
|
|
158
|
-
}
|
|
159
|
-
this.children = executeRender();
|
|
160
|
-
this.root?.popComponent();
|
|
161
|
-
this.root?.clearCurrent();
|
|
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);
|
|
168
|
-
// Queue onMount callbacks after children are mounted
|
|
169
|
-
// This ensures refs and other child lifecycle hooks run before parent onMount
|
|
170
|
-
instance.onMounts.forEach((cb) => {
|
|
171
|
-
this.root?.queueMount(cb);
|
|
172
|
-
});
|
|
173
|
-
return childElements;
|
|
174
|
-
}
|
|
175
|
-
patch(newNode) {
|
|
176
|
-
this.root?.setAsCurrent();
|
|
177
|
-
this.root?.pushComponent(this.instance);
|
|
178
|
-
for (const prop in newNode.props) {
|
|
179
|
-
this.instance.reactiveProps[prop] = newNode.props[prop];
|
|
180
|
-
}
|
|
181
|
-
this.root?.popComponent();
|
|
182
|
-
this.root?.clearCurrent();
|
|
183
|
-
}
|
|
184
|
-
unmount() {
|
|
185
|
-
this.instance.observer.dispose();
|
|
186
|
-
this.children.forEach((child) => child.unmount());
|
|
187
|
-
this.root?.queueUnmount(() => {
|
|
188
|
-
this.instance.onCleanups.forEach((cb) => {
|
|
189
|
-
try {
|
|
190
|
-
cb();
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
// Log error but continue executing remaining cleanups
|
|
194
|
-
console.error("Error during cleanup:", error);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function createReactiveProps(props) {
|
|
201
|
-
const reactiveProps = {};
|
|
202
|
-
for (const prop in props) {
|
|
203
|
-
const signal = new Signal();
|
|
204
|
-
Object.defineProperty(reactiveProps, prop, {
|
|
205
|
-
get() {
|
|
206
|
-
const observer = getCurrentObserver();
|
|
207
|
-
if (observer) {
|
|
208
|
-
observer.subscribeSignal(signal);
|
|
209
|
-
}
|
|
210
|
-
return props[prop];
|
|
211
|
-
},
|
|
212
|
-
set(value) {
|
|
213
|
-
if (props[prop] !== value) {
|
|
214
|
-
props[prop] = value;
|
|
215
|
-
signal.notify();
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return reactiveProps;
|
|
221
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { AbstractVNode, PatchOperation } from "./AbstractVNode";
|
|
2
|
-
import { Props, VNode, VFlags } from "./types";
|
|
3
|
-
export declare class ElementVNode extends AbstractVNode {
|
|
4
|
-
tag: string;
|
|
5
|
-
props: Props;
|
|
6
|
-
children: VNode[];
|
|
7
|
-
key?: string;
|
|
8
|
-
flags: VFlags;
|
|
9
|
-
private ref?;
|
|
10
|
-
private eventListeners?;
|
|
11
|
-
constructor(tag: string, { ref, ...props }: Props, children: VNode[], key?: string);
|
|
12
|
-
private computeFlags;
|
|
13
|
-
rerender(operations?: PatchOperation[]): void;
|
|
14
|
-
mount(parent?: VNode): Node;
|
|
15
|
-
/**
|
|
16
|
-
* An ELEMENT patch goes through three operations
|
|
17
|
-
* - Patch or replace the element
|
|
18
|
-
* - Patch the children
|
|
19
|
-
*/
|
|
20
|
-
patch(newNode: ElementVNode): void;
|
|
21
|
-
unmount(): void;
|
|
22
|
-
private setProp;
|
|
23
|
-
private patchProps;
|
|
24
|
-
private patchStyle;
|
|
25
|
-
private addEventListener;
|
|
26
|
-
}
|
|
27
|
-
//# sourceMappingURL=ElementVNode.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|