Skip to content

Commit e73d536

Browse files
committed
feat: support prop updates on objects with functions (#194)
1 parent 7603461 commit e73d536

File tree

2 files changed

+64
-7
lines changed

2 files changed

+64
-7
lines changed

src/client/index.js

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { addListener } from './listener.js';
33
// import { profiler } from './profiler.js';
44
import { nodes } from './svelte.js';
55

6+
const propClones = new Map();
7+
68
// @ts-ignore - for the app to call with `eval`
79
window['#SvelteDevTools'] = {
810
/**
@@ -12,10 +14,43 @@ window['#SvelteDevTools'] = {
1214
*/
1315
inject(id, key, value) {
1416
const { detail: component } = nodes.map.get(id) || {};
15-
component && component.$inject_state({ [key]: value });
17+
18+
if (component) {
19+
const clone = updateProp(propClones.get(id)[key], value);
20+
21+
component.$inject_state({
22+
[key]: clone,
23+
});
24+
}
1625
},
1726
};
1827

28+
/**
29+
* @param {*} orig
30+
* @param {*} value
31+
* @returns {any}
32+
*/
33+
function updateProp(orig, value, seen = new Map()) {
34+
switch (typeof value) {
35+
case 'object': {
36+
if (value === window || value === null) return null;
37+
if (Array.isArray(value)) return value.map((o, index) => updateProp(orig[index], o, seen));
38+
if (seen.has(value)) return seen.get(value);
39+
40+
/** @type {Record<string, any>} */
41+
const o = {};
42+
seen.set(value, o);
43+
for (const [key, v] of Object.entries(value)) {
44+
orig[key] = updateProp(orig[key], v, seen);
45+
}
46+
47+
return orig;
48+
}
49+
default:
50+
return value;
51+
}
52+
}
53+
1954
const previous = {
2055
/** @type {HTMLElement | null} */
2156
target: null,
@@ -132,19 +167,38 @@ function serialize(node) {
132167
switch (node.type) {
133168
case 'component': {
134169
const { $$: internal = {} } = node.detail;
135-
const ctx = clone(node.detail.$capture_state?.() || {});
170+
const nodeState = node.detail.$capture_state?.() || {};
136171
const bindings = Object.values(internal.bound || {}).map(
137172
/** @param {Function} f */ (f) => f.name,
138173
);
174+
175+
/** @type {Record<string, any>} */
176+
// clone original prop objects for update
177+
const _propClones = {};
139178
const props = Object.keys(internal.props || {}).flatMap((key) => {
140-
const value = ctx[key];
141-
delete ctx[key]; // deduplicate for ctx
179+
const prop = nodeState[key];
180+
181+
if (prop) {
182+
const prototypeDescriptors = Object.getOwnPropertyDescriptors(
183+
Object.getPrototypeOf(nodeState[key]),
184+
);
185+
const protoClone = Object.create(null, prototypeDescriptors);
186+
const clone = Object.create(protoClone, Object.getOwnPropertyDescriptors(prop));
187+
_propClones[key] = clone;
188+
}
189+
190+
const value = clone(prop);
191+
delete nodeState[key]; // deduplicate for ctx
142192
if (value === undefined) return [];
143193

144194
const bounded = bindings.some((f) => f.includes(key));
145195
return { key, value, bounded };
146196
});
147197

198+
propClones.set(res.id, _propClones);
199+
200+
const ctx = clone(nodeState);
201+
148202
res.detail = {
149203
attributes: props,
150204
listeners: Object.entries(internal.callbacks || {}).flatMap(([event, value]) =>

src/lib/panel/Expandable.svelte

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
case 'number':
2525
return value.toString();
2626
case 'object':
27-
return `{${Object.entries(value)
28-
.map(([key, value]) => `"${key}":${key == k ? v : stringify(value)}`)
29-
.join(',')}}`;
27+
// only return updated key
28+
if (k) {
29+
return `{${k}: ${v}}`;
30+
} else {
31+
return `{}`;
32+
}
3033
3134
default: // when is this ever the case?
3235
return value?.toString() ?? 'undefined';

0 commit comments

Comments
 (0)