rask-ui 0.2.6 → 0.2.8
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/createState.js +16 -0
- package/dist/vdom/AbstractVNode.d.ts +1 -1
- package/dist/vdom/AbstractVNode.d.ts.map +1 -1
- package/dist/vdom/AbstractVNode.js +13 -3
- package/dist/vdom/ComponentVNode.d.ts.map +1 -1
- package/dist/vdom/ComponentVNode.js +9 -5
- package/dist/vdom/ElementVNode.d.ts +4 -1
- package/dist/vdom/ElementVNode.d.ts.map +1 -1
- package/dist/vdom/ElementVNode.js +93 -4
- package/dist/vdom/FragmentVNode.d.ts.map +1 -1
- package/dist/vdom/FragmentVNode.js +8 -2
- package/dist/vdom/RootVNode.d.ts.map +1 -1
- package/dist/vdom/RootVNode.js +8 -1
- 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/createState.js
CHANGED
|
@@ -27,13 +27,29 @@ export function createState(state) {
|
|
|
27
27
|
return getProxy(state);
|
|
28
28
|
}
|
|
29
29
|
const proxyCache = new WeakMap();
|
|
30
|
+
const PROXY_MARKER = Symbol('isProxy');
|
|
30
31
|
function getProxy(value) {
|
|
32
|
+
// Check if already a proxy to avoid double-wrapping
|
|
33
|
+
if (PROXY_MARKER in value) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
31
36
|
if (proxyCache.has(value)) {
|
|
32
37
|
return proxyCache.get(value);
|
|
33
38
|
}
|
|
34
39
|
const signals = {};
|
|
35
40
|
const proxy = new Proxy(value, {
|
|
41
|
+
has(target, key) {
|
|
42
|
+
// Support the "in" operator check for PROXY_MARKER
|
|
43
|
+
if (key === PROXY_MARKER) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return Reflect.has(target, key);
|
|
47
|
+
},
|
|
36
48
|
get(target, key) {
|
|
49
|
+
// Mark this as a proxy to prevent double-wrapping
|
|
50
|
+
if (key === PROXY_MARKER) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
37
53
|
const value = Reflect.get(target, key);
|
|
38
54
|
if (typeof key === "symbol" || typeof value === "function") {
|
|
39
55
|
return value;
|
|
@@ -31,7 +31,7 @@ export declare abstract class AbstractVNode {
|
|
|
31
31
|
patchChildren(newChildren: VNode[]): {
|
|
32
32
|
children: VNode[];
|
|
33
33
|
hasChangedStructure: boolean;
|
|
34
|
-
operations
|
|
34
|
+
operations?: PatchOperation[];
|
|
35
35
|
};
|
|
36
36
|
applyPatchOperations(target: HTMLElement, operations: PatchOperation[]): void;
|
|
37
37
|
/**
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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,CAAC,EAAE,cAAc,EAAE,CAAC;KAC/B;IAqJD,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE;IAqCtE;;;;OAIG;IACH,SAAS,CAAC,eAAe;CA6B1B"}
|
|
@@ -21,7 +21,15 @@ export class AbstractVNode {
|
|
|
21
21
|
if (!this.children) {
|
|
22
22
|
throw new Error("This VNode has no element or children");
|
|
23
23
|
}
|
|
24
|
-
|
|
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;
|
|
25
33
|
}
|
|
26
34
|
getParentElement() {
|
|
27
35
|
let parent = this.parent;
|
|
@@ -55,13 +63,15 @@ export class AbstractVNode {
|
|
|
55
63
|
}
|
|
56
64
|
patchChildren(newChildren) {
|
|
57
65
|
const prevChildren = this.children;
|
|
66
|
+
if (newChildren.length === 0 && prevChildren.length === 0) {
|
|
67
|
+
return { children: [], hasChangedStructure: false };
|
|
68
|
+
}
|
|
58
69
|
// When there are only new children, we just mount them
|
|
59
|
-
if (
|
|
70
|
+
if (prevChildren.length === 0) {
|
|
60
71
|
newChildren.forEach((child) => child.mount(this));
|
|
61
72
|
return {
|
|
62
73
|
children: newChildren,
|
|
63
74
|
hasChangedStructure: true,
|
|
64
|
-
operations: [],
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
// If we want to remove all children, we just unmount the previous ones
|
|
@@ -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,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,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) {
|
|
@@ -99,11 +100,11 @@ export class ComponentVNode extends AbstractVNode {
|
|
|
99
100
|
this.children = children;
|
|
100
101
|
// So if a fragment is returned where we add new elements we can not safely
|
|
101
102
|
// add them yet, check Fragment for a potential later optimization
|
|
102
|
-
const hasAddOperation = operations
|
|
103
|
+
const hasAddOperation = operations?.some((operation) => operation.type === "add");
|
|
103
104
|
if (hasChangedStructure || hasAddOperation) {
|
|
104
105
|
this.parent?.rerender();
|
|
105
106
|
}
|
|
106
|
-
else if (operations
|
|
107
|
+
else if (operations?.length) {
|
|
107
108
|
this.parent?.rerender(operations);
|
|
108
109
|
}
|
|
109
110
|
this.root?.clearCurrent();
|
|
@@ -158,9 +159,12 @@ export class ComponentVNode extends AbstractVNode {
|
|
|
158
159
|
this.children = executeRender();
|
|
159
160
|
this.root?.popComponent();
|
|
160
161
|
this.root?.clearCurrent();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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);
|
|
164
168
|
// Queue onMount callbacks after children are mounted
|
|
165
169
|
// This ensures refs and other child lifecycle hooks run before parent onMount
|
|
166
170
|
instance.onMounts.forEach((cb) => {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { AbstractVNode, PatchOperation } from "./AbstractVNode";
|
|
2
|
-
import { Props, VNode } from "./types";
|
|
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);
|
|
12
|
+
private computeFlags;
|
|
11
13
|
rerender(operations?: PatchOperation[]): void;
|
|
12
14
|
mount(parent?: VNode): Node;
|
|
13
15
|
/**
|
|
@@ -19,6 +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
26
|
}
|
|
24
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,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,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,6 +17,31 @@ 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);
|
|
22
|
+
}
|
|
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;
|
|
19
45
|
}
|
|
20
46
|
rerender(operations) {
|
|
21
47
|
if (operations) {
|
|
@@ -60,14 +86,21 @@ export class ElementVNode extends AbstractVNode {
|
|
|
60
86
|
* - Patch the children
|
|
61
87
|
*/
|
|
62
88
|
patch(newNode) {
|
|
63
|
-
|
|
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;
|
|
64
97
|
this.props = newNode.props;
|
|
65
98
|
const { children, hasChangedStructure, operations } = this.patchChildren(newNode.children);
|
|
66
99
|
this.children = children;
|
|
67
100
|
if (hasChangedStructure) {
|
|
68
101
|
this.syncDOMChildren();
|
|
69
102
|
}
|
|
70
|
-
else {
|
|
103
|
+
else if (operations?.length) {
|
|
71
104
|
this.applyPatchOperations(this.getHTMLElement(), operations);
|
|
72
105
|
}
|
|
73
106
|
}
|
|
@@ -110,8 +143,64 @@ export class ElementVNode extends AbstractVNode {
|
|
|
110
143
|
}
|
|
111
144
|
setElementProp(elm, prop, value);
|
|
112
145
|
};
|
|
113
|
-
patchProps(newProps) {
|
|
114
|
-
|
|
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
|
+
}
|
|
115
204
|
}
|
|
116
205
|
addEventListener(type, cb) {
|
|
117
206
|
if (!this.eventListeners) {
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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,7 +18,12 @@ 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
28
|
rerender(operations) {
|
|
23
29
|
this.parent?.rerender(operations);
|
|
@@ -30,7 +36,7 @@ export class FragmentVNode extends AbstractVNode {
|
|
|
30
36
|
// handled with some additional detection, changing it to insertBefore. This can be
|
|
31
37
|
// done by passing this vnode up to the parent
|
|
32
38
|
this.rerender(hasChangedStructure ||
|
|
33
|
-
operations
|
|
39
|
+
operations?.some((operation) => operation.type === "add")
|
|
34
40
|
? undefined
|
|
35
41
|
: operations);
|
|
36
42
|
}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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"}
|
package/dist/vdom/RootVNode.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AbstractVNode } from "./AbstractVNode";
|
|
2
|
+
import { flattenNodes } from "./dom-utils";
|
|
2
3
|
// Global reference to the currently executing root
|
|
3
4
|
// Safe because JS is single-threaded - only one render executes at a time
|
|
4
5
|
export let currentRoot;
|
|
@@ -56,7 +57,13 @@ export class RootVNode extends AbstractVNode {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
mount() {
|
|
59
|
-
|
|
60
|
+
// Optimized: avoid intermediate arrays from map+flat
|
|
61
|
+
const childResults = [];
|
|
62
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
63
|
+
childResults.push(this.children[i].mount(this));
|
|
64
|
+
}
|
|
65
|
+
const result = flattenNodes(childResults);
|
|
66
|
+
return result.length === 1 ? result[0] : result;
|
|
60
67
|
}
|
|
61
68
|
patch() { }
|
|
62
69
|
rerender(operations) {
|
package/dist/vdom/dom-utils.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Efficiently flatten a Node or Node[] result without creating intermediate arrays.
|
|
3
|
+
* Used to optimize mount operations that return either Node or Node[].
|
|
4
|
+
*/
|
|
5
|
+
export declare function flattenNodes(items: (Node | Node[])[]): Node[];
|
|
1
6
|
export declare function replaceElementsOf(parent: HTMLElement, newChildren: Node | Node[]): void;
|
|
2
7
|
export declare function elementsToFragment(elm: Node | Node[]): Node;
|
|
3
8
|
export declare function removeElementRange(parent: Node, start: Node, end: Node): void;
|
|
4
9
|
export declare function setElementProp(elm: HTMLElement, key: string, value: unknown): void;
|
|
5
10
|
export declare function setElementAttr(elm: HTMLElement, key: string, value: string | null): void;
|
|
6
|
-
export declare function setElementStyle(elm: HTMLElement, value
|
|
11
|
+
export declare function setElementStyle(elm: HTMLElement, value?: string | Record<string, unknown> | null): void;
|
|
7
12
|
export declare function isEventProp(name: string): boolean;
|
|
8
13
|
export declare function setElementClass(elm: HTMLElement, value: string | Record<string, boolean> | null | undefined): void;
|
|
9
14
|
//# sourceMappingURL=dom-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-utils.d.ts","sourceRoot":"","sources":["../../src/vdom/dom-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,IAAI,GAAG,IAAI,EAAE,QAO3B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAc3D;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,QAQtE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAE3E;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,QAOrB;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"dom-utils.d.ts","sourceRoot":"","sources":["../../src/vdom/dom-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,CAa7D;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,IAAI,GAAG,IAAI,EAAE,QAO3B;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAc3D;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,QAQtE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAE3E;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,QAOrB;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,QAehD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,QA0B3D"}
|
package/dist/vdom/dom-utils.js
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Efficiently flatten a Node or Node[] result without creating intermediate arrays.
|
|
3
|
+
* Used to optimize mount operations that return either Node or Node[].
|
|
4
|
+
*/
|
|
5
|
+
export function flattenNodes(items) {
|
|
6
|
+
const result = [];
|
|
7
|
+
for (let i = 0; i < items.length; i++) {
|
|
8
|
+
const item = items[i];
|
|
9
|
+
if (Array.isArray(item)) {
|
|
10
|
+
for (let j = 0; j < item.length; j++) {
|
|
11
|
+
result.push(item[j]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
result.push(item);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
1
20
|
export function replaceElementsOf(parent, newChildren) {
|
|
2
21
|
if (Array.isArray(newChildren)) {
|
|
3
22
|
parent.replaceChildren(...newChildren);
|
|
@@ -42,7 +61,7 @@ export function setElementAttr(elm, key, value) {
|
|
|
42
61
|
}
|
|
43
62
|
}
|
|
44
63
|
export function setElementStyle(elm, value) {
|
|
45
|
-
if (value === null) {
|
|
64
|
+
if (value === null || value === undefined) {
|
|
46
65
|
elm.removeAttribute("style");
|
|
47
66
|
return;
|
|
48
67
|
}
|
package/dist/vdom/types.d.ts
CHANGED
|
@@ -5,4 +5,16 @@ import { RootVNode } from "./RootVNode";
|
|
|
5
5
|
import { TextVNode } from "./TextVNode";
|
|
6
6
|
export type VNode = ElementVNode | FragmentVNode | ComponentVNode | TextVNode | RootVNode;
|
|
7
7
|
export type Props = Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* Bit flags to optimize VNode property checks.
|
|
10
|
+
* Pre-computed during VNode creation to avoid repeated conditional checks.
|
|
11
|
+
*/
|
|
12
|
+
export declare const enum VFlags {
|
|
13
|
+
None = 0,
|
|
14
|
+
HasProps = 1,// Has any props at all
|
|
15
|
+
HasClass = 2,// Has class or className prop
|
|
16
|
+
HasStyle = 4,// Has style prop
|
|
17
|
+
HasEvents = 8,// Has event listeners (onXxx props)
|
|
18
|
+
HasDataAttrs = 16
|
|
19
|
+
}
|
|
8
20
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/vdom/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vdom/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,KAAK,GACb,YAAY,GACZ,aAAa,GACb,cAAc,GACd,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vdom/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,KAAK,GACb,YAAY,GACZ,aAAa,GACb,cAAc,GACd,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5C;;;GAGG;AACH,0BAAkB,MAAM;IACtB,IAAI,IAAI;IACR,QAAQ,IAAS,CAAQ,uBAAuB;IAChD,QAAQ,IAAS,CAAQ,8BAA8B;IACvD,QAAQ,IAAS,CAAQ,iBAAiB;IAC1C,SAAS,IAAS,CAAO,oCAAoC;IAC7D,YAAY,KAAS;CACtB"}
|