kiru 1.4.0 → 1.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/dist/hooks/setup.d.ts.map +1 -1
- package/dist/hooks/setup.js +100 -38
- package/dist/hooks/setup.js.map +1 -1
- package/package.json +1 -1
- package/src/hooks/setup.ts +119 -52
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/hooks/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/hooks/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAanD,MAAM,WAAW,KAAK,CAAC,KAAK,SAAS,EAAE;IACrC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,SAAS,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,KAAK,CAAC,KAC/D,MAAM,CAAC,CAAC,CAAC,CAAA;IACd,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IAE3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,SAAS,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;CACrE;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,SAAS,EAAE,KAAK,KAAK,CAAC,KAAK,CAAC,CAkBtD"}
|
package/dist/hooks/setup.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { signal, Signal } from "../signals/base.js";
|
|
2
|
-
import { createVNodeId } from "../utils/vdom.js";
|
|
2
|
+
import { createVNodeId, isVNodeDeleted } from "../utils/vdom.js";
|
|
3
3
|
import { __DEV__ } from "../env.js";
|
|
4
4
|
import { node, setups } from "../globals.js";
|
|
5
5
|
import { tracking, } from "../signals/tracking.js";
|
|
6
6
|
import { registerVNodeCleanup } from "../utils/index.js";
|
|
7
|
+
let currentAccessedPaths = null;
|
|
8
|
+
const OWN_KEYS = `__KEYS__`;
|
|
7
9
|
/**
|
|
8
10
|
* Creates a per‑VNode setup context that can be used during
|
|
9
11
|
* component setup to derive props into signals.
|
|
@@ -29,6 +31,7 @@ export function setup() {
|
|
|
29
31
|
}
|
|
30
32
|
function createSetup(vNode) {
|
|
31
33
|
let id;
|
|
34
|
+
let propsProxy;
|
|
32
35
|
const propSyncs = (vNode.propSyncs = []);
|
|
33
36
|
let prevIndex = -1;
|
|
34
37
|
// Always points at latest props (updated in propSync) so selector and subs see current props
|
|
@@ -39,8 +42,8 @@ function createSetup(vNode) {
|
|
|
39
42
|
const old = currentProps.current;
|
|
40
43
|
const skip = new Set();
|
|
41
44
|
for (const entry of deriveEntries) {
|
|
42
|
-
if (entry.
|
|
43
|
-
propsUnchangedAtPaths(old, p, entry.
|
|
45
|
+
if (entry.paths.size > 0 &&
|
|
46
|
+
propsUnchangedAtPaths(old, p, entry.paths)) {
|
|
44
47
|
skip.add(entry);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -62,10 +65,12 @@ function createSetup(vNode) {
|
|
|
62
65
|
const accessedPaths = new Set();
|
|
63
66
|
function sync() {
|
|
64
67
|
accessedPaths.clear();
|
|
65
|
-
const propsProxy =
|
|
68
|
+
const propsProxy = createProxy(currentProps.current);
|
|
66
69
|
const observations = new Map();
|
|
67
70
|
tracking.stack.push(observations);
|
|
71
|
+
currentAccessedPaths = accessedPaths;
|
|
68
72
|
const value = selector(propsProxy);
|
|
73
|
+
currentAccessedPaths = null;
|
|
69
74
|
tracking.stack.pop();
|
|
70
75
|
// Always assign and notify so the component re-renders when the derived value changes
|
|
71
76
|
// (e.g. when parent passes a different signal ref like toggle switching count/double).
|
|
@@ -88,7 +93,7 @@ function createSetup(vNode) {
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
sync();
|
|
91
|
-
const entry = { run: sync, accessedPaths };
|
|
96
|
+
const entry = { run: sync, paths: accessedPaths };
|
|
92
97
|
deriveEntries.push(entry);
|
|
93
98
|
deriveCleanups.push(() => {
|
|
94
99
|
unsubs.forEach((u) => u());
|
|
@@ -102,6 +107,13 @@ function createSetup(vNode) {
|
|
|
102
107
|
get id() {
|
|
103
108
|
if (!id) {
|
|
104
109
|
id = signal(createVNodeId(vNode));
|
|
110
|
+
if (isVNodeDeleted(vNode)) {
|
|
111
|
+
return id;
|
|
112
|
+
}
|
|
113
|
+
if (node.current !== vNode) {
|
|
114
|
+
// @ts-expect-error
|
|
115
|
+
registerVNodeCleanup(vNode, id.$id, Signal.dispose.bind(null, id));
|
|
116
|
+
}
|
|
105
117
|
prevIndex = vNode.index;
|
|
106
118
|
propSyncs.push(() => {
|
|
107
119
|
if (prevIndex !== vNode.index) {
|
|
@@ -113,49 +125,99 @@ function createSetup(vNode) {
|
|
|
113
125
|
return id;
|
|
114
126
|
},
|
|
115
127
|
get props() {
|
|
116
|
-
return
|
|
128
|
+
return (propsProxy ?? (propsProxy = new Proxy({}, {
|
|
129
|
+
get(_, key) {
|
|
130
|
+
if (typeof key === "symbol")
|
|
131
|
+
return Reflect.get(currentProps.current, key);
|
|
132
|
+
const v = currentProps.current[key];
|
|
133
|
+
if (v !== null && typeof v === "object" && !Signal.isSignal(v)) {
|
|
134
|
+
return createProxy(v);
|
|
135
|
+
}
|
|
136
|
+
return v;
|
|
137
|
+
},
|
|
138
|
+
})));
|
|
117
139
|
},
|
|
118
140
|
};
|
|
119
141
|
return setupResult;
|
|
120
142
|
}
|
|
121
|
-
function propsUnchangedAtPaths(oldProps, newProps,
|
|
122
|
-
for (const path of
|
|
123
|
-
|
|
124
|
-
|
|
143
|
+
function propsUnchangedAtPaths(oldProps, newProps, accessedPaths) {
|
|
144
|
+
outer: for (const path of accessedPaths) {
|
|
145
|
+
let a = oldProps;
|
|
146
|
+
let b = newProps;
|
|
147
|
+
for (let i = 0; i < path.length; i++) {
|
|
148
|
+
const key = path[i];
|
|
149
|
+
// Sentinel: caller iterated keys of the object at this path —
|
|
150
|
+
// re-run if the key sets differ
|
|
151
|
+
if (key === OWN_KEYS) {
|
|
152
|
+
if (a === b)
|
|
153
|
+
continue outer;
|
|
154
|
+
if (a == null ||
|
|
155
|
+
b == null ||
|
|
156
|
+
typeof a !== "object" ||
|
|
157
|
+
typeof b !== "object") {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const aKeys = Object.keys(a);
|
|
161
|
+
const bKeys = Object.keys(b);
|
|
162
|
+
if (aKeys.length !== bKeys.length)
|
|
163
|
+
return false;
|
|
164
|
+
for (const k of aKeys) {
|
|
165
|
+
if (!(k in b))
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
continue outer;
|
|
169
|
+
}
|
|
170
|
+
if (a === b)
|
|
171
|
+
continue outer;
|
|
172
|
+
if (a == null ||
|
|
173
|
+
b == null ||
|
|
174
|
+
typeof a !== "object" ||
|
|
175
|
+
typeof b !== "object") {
|
|
176
|
+
if (!Object.is(a, b))
|
|
177
|
+
return false;
|
|
178
|
+
continue outer;
|
|
179
|
+
}
|
|
180
|
+
a = a[key];
|
|
181
|
+
b = b[key];
|
|
125
182
|
}
|
|
183
|
+
if (!Object.is(a, b))
|
|
184
|
+
return false;
|
|
126
185
|
}
|
|
127
186
|
return true;
|
|
128
187
|
}
|
|
129
|
-
|
|
130
|
-
let cur = obj;
|
|
131
|
-
for (const key of path.split(".")) {
|
|
132
|
-
if (cur == null || typeof cur !== "object")
|
|
133
|
-
return undefined;
|
|
134
|
-
cur = cur[key];
|
|
135
|
-
}
|
|
136
|
-
return cur;
|
|
137
|
-
}
|
|
188
|
+
const proxyCache = new WeakMap();
|
|
138
189
|
/**
|
|
139
|
-
* Proxy that records paths and
|
|
140
|
-
*
|
|
141
|
-
* signal refs. Container objects (e.g. "data") are new every render and would
|
|
142
|
-
* always fail the skip.
|
|
190
|
+
* Proxy that records accessed paths and primitives into the current
|
|
191
|
+
* tracking context (currentAccessedPaths).
|
|
143
192
|
*/
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
193
|
+
function createProxy(source, path = []) {
|
|
194
|
+
let cached = proxyCache.get(source);
|
|
195
|
+
if (!cached) {
|
|
196
|
+
cached = new Proxy(source, {
|
|
197
|
+
get(holder, key) {
|
|
198
|
+
if (typeof key === "symbol")
|
|
199
|
+
return Reflect.get(holder, key);
|
|
200
|
+
const keyPath = [...path, key];
|
|
201
|
+
const v = holder[key];
|
|
202
|
+
if (v !== null && typeof v === "object" && !Signal.isSignal(v)) {
|
|
203
|
+
return createProxy(v, keyPath);
|
|
204
|
+
}
|
|
205
|
+
currentAccessedPaths?.add(keyPath);
|
|
151
206
|
return v;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
207
|
+
},
|
|
208
|
+
has(holder, key) {
|
|
209
|
+
if (typeof key === "symbol")
|
|
210
|
+
return Reflect.has(holder, key);
|
|
211
|
+
currentAccessedPaths?.add([...path, key]);
|
|
212
|
+
return key in holder;
|
|
213
|
+
},
|
|
214
|
+
ownKeys(holder) {
|
|
215
|
+
currentAccessedPaths?.add([...path, OWN_KEYS]);
|
|
216
|
+
return Reflect.ownKeys(holder);
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
proxyCache.set(source, cached);
|
|
220
|
+
}
|
|
221
|
+
return cached;
|
|
160
222
|
}
|
|
161
223
|
//# sourceMappingURL=setup.js.map
|
package/dist/hooks/setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/hooks/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/hooks/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,QAAQ,GAET,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAExD,IAAI,oBAAoB,GAAyB,IAAI,CAAA;AACrD,MAAM,QAAQ,GAAG,UAAU,CAAA;AAW3B;;;;;GAKG;AACH,MAAM,UAAU,KAAK;IACnB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAQ,CAAA;IAC3B,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;QACnE,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAE,CAAA;IAC3B,CAAC;IAED,MAAM,KAAK,GAAG,WAAW,CAAQ,KAAK,CAAC,CAAA;IACvC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACxB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,WAAW,CAAmB,KAAiB;IACtD,IAAI,EAAkB,CAAA;IACtB,IAAI,UAAyB,CAAA;IAG7B,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAuC,CAAA;IAE9E,IAAI,SAAS,GAAG,CAAC,CAAC,CAAA;IAElB,6FAA6F;IAC7F,MAAM,YAAY,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,KAAK,EAAmB,EAAE,CAAA;IACrE,MAAM,cAAc,GAAsB,EAAE,CAAA;IAG5C,MAAM,aAAa,GAAkB,EAAE,CAAA;IAEvC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACnB,MAAM,GAAG,GAAG,YAAY,CAAC,OAAkC,CAAA;QAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAe,CAAA;QACnC,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAClC,IACE,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;gBACpB,qBAAqB,CAAC,GAAG,EAAE,CAA4B,EAAE,KAAK,CAAC,KAAK,CAAC,EACrE,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACjB,CAAC;QACH,CAAC;QACD,YAAY,CAAC,OAAO,GAAG,CAAC,CAAA;QACxB,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,GAAG,EAAE,CAAA;QACnC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE;QAC9C,KAAK,MAAM,OAAO,IAAI,cAAc;YAAE,OAAO,EAAE,CAAA;QAC/C,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,MAAM,WAAW,GAAiB;QAChC,MAAM,CACJ,QAAkE;YAElE,MAAM,SAAS,GAAG,MAAM,CAAC,SAAU,CAAc,CAAA;YACjD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAA;YAC5C,MAAM,aAAa,GAAG,IAAI,GAAG,EAAY,CAAA;YAEzC,SAAS,IAAI;gBACX,aAAa,CAAC,KAAK,EAAE,CAAA;gBACrB,MAAM,UAAU,GAAG,WAAW,CAC5B,YAAY,CAAC,OAAkC,CAC/B,CAAA;gBAClB,MAAM,YAAY,GAA8B,IAAI,GAAG,EAAE,CAAA;gBACzD,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;gBACjC,oBAAoB,GAAG,aAAa,CAAA;gBACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAA;gBAClC,oBAAoB,GAAG,IAAI,CAAA;gBAC3B,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;gBACpB,sFAAsF;gBACtF,uFAAuF;gBACvF,SAAS,CAAC,KAAK,GAAG,KAAK,CAAA;gBAEvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;oBAClC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;wBAC3B,KAAK,EAAE,CAAA;wBACP,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;oBACpB,CAAC;gBACH,CAAC;gBACD,KAAK,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,IAAI,YAAY,EAAE,CAAC;oBAC9C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;wBACrB,IAAI,CAAC;4BACH,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;wBAC9C,CAAC;wBAAC,MAAM,CAAC;4BACP,qDAAqD;wBACvD,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,EAAE,CAAA;YACN,MAAM,KAAK,GAAgB,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;YAC9D,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACzB,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE;gBACvB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;gBAC1B,MAAM,CAAC,KAAK,EAAE,CAAA;gBACd,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;gBACtC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAAE,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;YAC1C,CAAC,CAAC,CAAA;YAEF,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,IAAI,EAAE;YACJ,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,EAAE,GAAG,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;gBACjC,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,OAAO,EAAE,CAAA;gBACX,CAAC;gBACD,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;oBAC3B,mBAAmB;oBACnB,oBAAoB,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;gBACpE,CAAC;gBACD,SAAS,GAAG,KAAK,CAAC,KAAK,CAAA;gBACvB,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;oBAClB,IAAI,SAAS,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;wBAC9B,EAAE,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;wBAC/B,SAAS,GAAG,KAAK,CAAC,KAAK,CAAA;oBACzB,CAAC;gBACH,CAAC,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,EAAE,CAAA;QACX,CAAC;QACD,IAAI,KAAK;YACP,OAAO,CAAC,UAAU,KAAV,UAAU,GAAK,IAAI,KAAK,CAC9B,EAAE,EACF;gBACE,GAAG,CAAC,CAAC,EAAE,GAAG;oBACR,IAAI,OAAO,GAAG,KAAK,QAAQ;wBACzB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,OAAc,EAAE,GAAG,CAAC,CAAA;oBACtD,MAAM,CAAC,GAAI,YAAY,CAAC,OAAe,CAAC,GAAG,CAAC,CAAA;oBAC5C,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC/D,OAAO,WAAW,CAAC,CAAC,CAAC,CAAA;oBACvB,CAAC;oBACD,OAAO,CAAC,CAAA;gBACV,CAAC;aACF,CACe,EAAC,CAAA;QACrB,CAAC;KACF,CAAA;IACD,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,SAAS,qBAAqB,CAC5B,QAAiC,EACjC,QAAiC,EACjC,aAA4B;IAE5B,KAAK,EAAE,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACxC,IAAI,CAAC,GAAY,QAAQ,CAAA;QACzB,IAAI,CAAC,GAAY,QAAQ,CAAA;QAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;YAEnB,8DAA8D;YAC9D,gCAAgC;YAChC,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACrB,IAAI,CAAC,KAAK,CAAC;oBAAE,SAAS,KAAK,CAAA;gBAC3B,IACE,CAAC,IAAI,IAAI;oBACT,CAAC,IAAI,IAAI;oBACT,OAAO,CAAC,KAAK,QAAQ;oBACrB,OAAO,CAAC,KAAK,QAAQ,EACrB,CAAC;oBACD,OAAO,KAAK,CAAA;gBACd,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;gBAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;gBAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;oBAAE,OAAO,KAAK,CAAA;gBAC/C,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACtB,IAAI,CAAC,CAAC,CAAC,IAAK,CAAY,CAAC;wBAAE,OAAO,KAAK,CAAA;gBACzC,CAAC;gBACD,SAAS,KAAK,CAAA;YAChB,CAAC;YAED,IAAI,CAAC,KAAK,CAAC;gBAAE,SAAS,KAAK,CAAA;YAC3B,IACE,CAAC,IAAI,IAAI;gBACT,CAAC,IAAI,IAAI;gBACT,OAAO,CAAC,KAAK,QAAQ;gBACrB,OAAO,CAAC,KAAK,QAAQ,EACrB,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAA;gBAClC,SAAS,KAAK,CAAA;YAChB,CAAC;YACD,CAAC,GAAI,CAA6B,CAAC,GAAG,CAAC,CAAA;YACvC,CAAC,GAAI,CAA6B,CAAC,GAAG,CAAC,CAAA;QACzC,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;IACpC,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,UAAU,GAAG,IAAI,OAAO,EAAe,CAAA;AAE7C;;;GAGG;AACH,SAAS,WAAW,CAClB,MAAS,EACT,OAAiB,EAAE;IAEnB,IAAI,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAEnC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE;YACzB,GAAG,CAAC,MAAM,EAAE,GAAoB;gBAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ;oBAAE,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;gBAE5D,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,EAAE,GAAa,CAAC,CAAA;gBACxC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAa,CAAC,CAAA;gBAE/B,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC/D,OAAO,WAAW,CAAC,CAA4B,EAAE,OAAO,CAAC,CAAA;gBAC3D,CAAC;gBAED,oBAAoB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;gBAClC,OAAO,CAAC,CAAA;YACV,CAAC;YACD,GAAG,CAAC,MAAM,EAAE,GAAoB;gBAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ;oBAAE,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;gBAC5D,oBAAoB,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAa,CAAC,CAAC,CAAA;gBACnD,OAAO,GAAG,IAAI,MAAM,CAAA;YACtB,CAAC;YACD,OAAO,CAAC,MAAM;gBACZ,oBAAoB,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAA;gBAC9C,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;YAChC,CAAC;SACF,CAAC,CAAA;QAEF,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/package.json
CHANGED
package/src/hooks/setup.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { signal, Signal } from "../signals/base.js"
|
|
2
|
-
import { createVNodeId } from "../utils/vdom.js"
|
|
2
|
+
import { createVNodeId, isVNodeDeleted } from "../utils/vdom.js"
|
|
3
3
|
import { __DEV__ } from "../env.js"
|
|
4
4
|
import { node, setups } from "../globals.js"
|
|
5
5
|
import {
|
|
@@ -8,11 +8,15 @@ import {
|
|
|
8
8
|
} from "../signals/tracking.js"
|
|
9
9
|
import { registerVNodeCleanup } from "../utils/index.js"
|
|
10
10
|
|
|
11
|
+
let currentAccessedPaths: Set<string[]> | null = null
|
|
12
|
+
const OWN_KEYS = `__KEYS__`
|
|
13
|
+
|
|
11
14
|
export interface Setup<Props extends {}> {
|
|
12
15
|
readonly derive: <T>(
|
|
13
16
|
selector: (props: Props extends Kiru.FC<infer P> ? P : Props) => T
|
|
14
17
|
) => Signal<T>
|
|
15
18
|
readonly id: Signal<string>
|
|
19
|
+
// Not reactive — for use in render functions only
|
|
16
20
|
readonly props: Readonly<Props extends Kiru.FC<infer P> ? P : Props>
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -41,8 +45,10 @@ export function setup<Props extends {}>(): Setup<Props> {
|
|
|
41
45
|
setups.set(vNode, setup)
|
|
42
46
|
return setup
|
|
43
47
|
}
|
|
48
|
+
|
|
44
49
|
function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
45
50
|
let id: Signal<string>
|
|
51
|
+
let propsProxy: InferredProps
|
|
46
52
|
|
|
47
53
|
type InferredProps = Props extends Kiru.FC<infer R> ? R : Props
|
|
48
54
|
const propSyncs = (vNode.propSyncs = []) as ((props: InferredProps) => void)[]
|
|
@@ -53,7 +59,7 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
53
59
|
const currentProps = { current: { ...vNode.props } as InferredProps }
|
|
54
60
|
const deriveCleanups: Array<() => void> = []
|
|
55
61
|
|
|
56
|
-
type DeriveEntry = { run: () => void;
|
|
62
|
+
type DeriveEntry = { run: () => void; paths: Set<string[]> }
|
|
57
63
|
const deriveEntries: DeriveEntry[] = []
|
|
58
64
|
|
|
59
65
|
propSyncs.push((p) => {
|
|
@@ -61,12 +67,8 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
61
67
|
const skip = new Set<DeriveEntry>()
|
|
62
68
|
for (const entry of deriveEntries) {
|
|
63
69
|
if (
|
|
64
|
-
entry.
|
|
65
|
-
propsUnchangedAtPaths(
|
|
66
|
-
old,
|
|
67
|
-
p as Record<string, unknown>,
|
|
68
|
-
entry.accessedPaths
|
|
69
|
-
)
|
|
70
|
+
entry.paths.size > 0 &&
|
|
71
|
+
propsUnchangedAtPaths(old, p as Record<string, unknown>, entry.paths)
|
|
70
72
|
) {
|
|
71
73
|
skip.add(entry)
|
|
72
74
|
}
|
|
@@ -88,17 +90,18 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
88
90
|
) {
|
|
89
91
|
const resultSig = signal(undefined!) as Signal<T>
|
|
90
92
|
const unsubs = new Map<string, () => void>()
|
|
91
|
-
const accessedPaths = new Set<string>()
|
|
93
|
+
const accessedPaths = new Set<string[]>()
|
|
92
94
|
|
|
93
95
|
function sync() {
|
|
94
96
|
accessedPaths.clear()
|
|
95
|
-
const propsProxy =
|
|
96
|
-
currentProps.current as Record<string, unknown
|
|
97
|
-
accessedPaths
|
|
97
|
+
const propsProxy = createProxy(
|
|
98
|
+
currentProps.current as Record<string, unknown>
|
|
98
99
|
) as InferredProps
|
|
99
100
|
const observations: TrackingStackObservations = new Map()
|
|
100
101
|
tracking.stack.push(observations)
|
|
102
|
+
currentAccessedPaths = accessedPaths
|
|
101
103
|
const value = selector(propsProxy)
|
|
104
|
+
currentAccessedPaths = null
|
|
102
105
|
tracking.stack.pop()
|
|
103
106
|
// Always assign and notify so the component re-renders when the derived value changes
|
|
104
107
|
// (e.g. when parent passes a different signal ref like toggle switching count/double).
|
|
@@ -122,7 +125,7 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
sync()
|
|
125
|
-
const entry: DeriveEntry = { run: sync, accessedPaths }
|
|
128
|
+
const entry: DeriveEntry = { run: sync, paths: accessedPaths }
|
|
126
129
|
deriveEntries.push(entry)
|
|
127
130
|
deriveCleanups.push(() => {
|
|
128
131
|
unsubs.forEach((u) => u())
|
|
@@ -136,6 +139,13 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
136
139
|
get id() {
|
|
137
140
|
if (!id) {
|
|
138
141
|
id = signal(createVNodeId(vNode))
|
|
142
|
+
if (isVNodeDeleted(vNode)) {
|
|
143
|
+
return id
|
|
144
|
+
}
|
|
145
|
+
if (node.current !== vNode) {
|
|
146
|
+
// @ts-expect-error
|
|
147
|
+
registerVNodeCleanup(vNode, id.$id, Signal.dispose.bind(null, id))
|
|
148
|
+
}
|
|
139
149
|
prevIndex = vNode.index
|
|
140
150
|
propSyncs.push(() => {
|
|
141
151
|
if (prevIndex !== vNode.index) {
|
|
@@ -148,61 +158,118 @@ function createSetup<Props extends {}>(vNode: Kiru.VNode): Setup<Props> {
|
|
|
148
158
|
return id
|
|
149
159
|
},
|
|
150
160
|
get props() {
|
|
151
|
-
return
|
|
161
|
+
return (propsProxy ??= new Proxy(
|
|
162
|
+
{},
|
|
163
|
+
{
|
|
164
|
+
get(_, key) {
|
|
165
|
+
if (typeof key === "symbol")
|
|
166
|
+
return Reflect.get(currentProps.current as any, key)
|
|
167
|
+
const v = (currentProps.current as any)[key]
|
|
168
|
+
if (v !== null && typeof v === "object" && !Signal.isSignal(v)) {
|
|
169
|
+
return createProxy(v)
|
|
170
|
+
}
|
|
171
|
+
return v
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
) as InferredProps)
|
|
152
175
|
},
|
|
153
176
|
}
|
|
154
177
|
return setupResult
|
|
155
178
|
}
|
|
179
|
+
|
|
156
180
|
function propsUnchangedAtPaths(
|
|
157
181
|
oldProps: Record<string, unknown>,
|
|
158
182
|
newProps: Record<string, unknown>,
|
|
159
|
-
|
|
183
|
+
accessedPaths: Set<string[]>
|
|
160
184
|
): boolean {
|
|
161
|
-
for (const path of
|
|
162
|
-
|
|
163
|
-
|
|
185
|
+
outer: for (const path of accessedPaths) {
|
|
186
|
+
let a: unknown = oldProps
|
|
187
|
+
let b: unknown = newProps
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < path.length; i++) {
|
|
190
|
+
const key = path[i]
|
|
191
|
+
|
|
192
|
+
// Sentinel: caller iterated keys of the object at this path —
|
|
193
|
+
// re-run if the key sets differ
|
|
194
|
+
if (key === OWN_KEYS) {
|
|
195
|
+
if (a === b) continue outer
|
|
196
|
+
if (
|
|
197
|
+
a == null ||
|
|
198
|
+
b == null ||
|
|
199
|
+
typeof a !== "object" ||
|
|
200
|
+
typeof b !== "object"
|
|
201
|
+
) {
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
const aKeys = Object.keys(a)
|
|
205
|
+
const bKeys = Object.keys(b)
|
|
206
|
+
if (aKeys.length !== bKeys.length) return false
|
|
207
|
+
for (const k of aKeys) {
|
|
208
|
+
if (!(k in (b as object))) return false
|
|
209
|
+
}
|
|
210
|
+
continue outer
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (a === b) continue outer
|
|
214
|
+
if (
|
|
215
|
+
a == null ||
|
|
216
|
+
b == null ||
|
|
217
|
+
typeof a !== "object" ||
|
|
218
|
+
typeof b !== "object"
|
|
219
|
+
) {
|
|
220
|
+
if (!Object.is(a, b)) return false
|
|
221
|
+
continue outer
|
|
222
|
+
}
|
|
223
|
+
a = (a as Record<string, unknown>)[key]
|
|
224
|
+
b = (b as Record<string, unknown>)[key]
|
|
164
225
|
}
|
|
226
|
+
|
|
227
|
+
if (!Object.is(a, b)) return false
|
|
165
228
|
}
|
|
229
|
+
|
|
166
230
|
return true
|
|
167
231
|
}
|
|
168
232
|
|
|
169
|
-
|
|
170
|
-
let cur: unknown = obj
|
|
171
|
-
for (const key of path.split(".")) {
|
|
172
|
-
if (cur == null || typeof cur !== "object") return undefined
|
|
173
|
-
cur = (cur as Record<string, unknown>)[key]
|
|
174
|
-
}
|
|
175
|
-
return cur
|
|
176
|
-
}
|
|
233
|
+
const proxyCache = new WeakMap<object, any>()
|
|
177
234
|
|
|
178
235
|
/**
|
|
179
|
-
* Proxy that records paths and
|
|
180
|
-
*
|
|
181
|
-
* signal refs. Container objects (e.g. "data") are new every render and would
|
|
182
|
-
* always fail the skip.
|
|
236
|
+
* Proxy that records accessed paths and primitives into the current
|
|
237
|
+
* tracking context (currentAccessedPaths).
|
|
183
238
|
*/
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
pathPrefix?: string
|
|
239
|
+
function createProxy<P extends Record<string, unknown>>(
|
|
240
|
+
source: P,
|
|
241
|
+
path: string[] = []
|
|
188
242
|
): P {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
243
|
+
let cached = proxyCache.get(source)
|
|
244
|
+
|
|
245
|
+
if (!cached) {
|
|
246
|
+
cached = new Proxy(source, {
|
|
247
|
+
get(holder, key: string | symbol) {
|
|
248
|
+
if (typeof key === "symbol") return Reflect.get(holder, key)
|
|
249
|
+
|
|
250
|
+
const keyPath = [...path, key as string]
|
|
251
|
+
const v = holder[key as string]
|
|
252
|
+
|
|
253
|
+
if (v !== null && typeof v === "object" && !Signal.isSignal(v)) {
|
|
254
|
+
return createProxy(v as Record<string, unknown>, keyPath)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
currentAccessedPaths?.add(keyPath)
|
|
195
258
|
return v
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
259
|
+
},
|
|
260
|
+
has(holder, key: string | symbol) {
|
|
261
|
+
if (typeof key === "symbol") return Reflect.has(holder, key)
|
|
262
|
+
currentAccessedPaths?.add([...path, key as string])
|
|
263
|
+
return key in holder
|
|
264
|
+
},
|
|
265
|
+
ownKeys(holder) {
|
|
266
|
+
currentAccessedPaths?.add([...path, OWN_KEYS])
|
|
267
|
+
return Reflect.ownKeys(holder)
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
proxyCache.set(source, cached)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return cached
|
|
208
275
|
}
|