Skip to content

Commit 593cb07

Browse files
committed
feat: support prop updates on objects with functions (#194)
1 parent fb8a72d commit 593cb07

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

src/client/index.js

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

6+
const propClones = new Map();
7+
8+
/**
9+
* @param {*} orig
10+
* @param {*} value
11+
* @returns {any}
12+
*/
13+
function updateProp(orig, value, seen = new Map()) {
14+
switch (typeof value) {
15+
case 'object': {
16+
if (value === window || value === null) return null;
17+
if (Array.isArray(value)) return value.map((o, index) => updateProp(orig[index], o, seen));
18+
if (seen.has(value)) return seen.get(value);
19+
20+
/** @type {Record<string, any>} */
21+
const o = {};
22+
seen.set(value, o);
23+
for (const [key, v] of Object.entries(value)) {
24+
orig[key] = updateProp(orig[key], v, seen);
25+
}
26+
27+
return orig;
28+
}
29+
default:
30+
return value;
31+
}
32+
}
33+
634
// @ts-ignore - possibly find an alternative
735
window.__svelte_devtools_inject_state = function (id, key, value) {
836
const { detail: component } = getNode(id) || {};
9-
component && component.$inject_state({ [key]: value });
37+
if (component) {
38+
const clone = updateProp(propClones.get(id)[key], value);
39+
40+
component.$inject_state({
41+
[key]: clone,
42+
});
43+
}
1044
};
1145

1246
// @ts-ignore - possibly find an alternative
@@ -131,19 +165,38 @@ function serialize(node) {
131165
switch (node.type) {
132166
case 'component': {
133167
const { $$: internal = {} } = node.detail;
134-
const ctx = clone(node.detail.$capture_state?.() || {});
168+
const nodeState = node.detail.$capture_state?.() || {};
135169
const bindings = Object.values(internal.bound || {}).map(
136170
/** @param {Function} f */ (f) => f.name,
137171
);
172+
173+
/** @type {Record<string, any>} */
174+
// clone original prop objects for update
175+
const _propClones = {};
138176
const props = Object.keys(internal.props || {}).flatMap((key) => {
139-
const value = ctx[key];
140-
delete ctx[key]; // deduplicate for ctx
177+
const prop = nodeState[key];
178+
179+
if (prop) {
180+
const prototypeDescriptors = Object.getOwnPropertyDescriptors(
181+
Object.getPrototypeOf(nodeState[key]),
182+
);
183+
const protoClone = Object.create(null, prototypeDescriptors);
184+
const clone = Object.create(protoClone, Object.getOwnPropertyDescriptors(prop));
185+
_propClones[key] = clone;
186+
}
187+
188+
const value = clone(prop);
189+
delete nodeState[key]; // deduplicate for ctx
141190
if (value === undefined) return [];
142191

143192
const bounded = bindings.some((f) => f.includes(key));
144193
return { key, value, bounded };
145194
});
146195

196+
propClones.set(res.id, _propClones);
197+
198+
const ctx = clone(nodeState);
199+
147200
res.detail = {
148201
attributes: props,
149202
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
@@ -20,9 +20,12 @@
2020
case 'number':
2121
return value.toString();
2222
case 'object':
23-
return `{${Object.entries(value)
24-
.map(([key, value]) => `"${key}":${key == k ? v : stringify(value)}`)
25-
.join(',')}}`;
23+
// only return updated key
24+
if (k) {
25+
return `{${k}: ${v}}`;
26+
} else {
27+
return `{}`;
28+
}
2629
2730
default: // when is this ever the case?
2831
return value?.toString() ?? 'undefined';

0 commit comments

Comments
 (0)