Skip to content

Commit fcdfd35

Browse files
committed
feat: allow $inspect reactivity map, set, date
1 parent be84242 commit fcdfd35

File tree

9 files changed

+160
-2
lines changed

9 files changed

+160
-2
lines changed

.changeset/tiny-poems-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
feat: allow inspect reactivity map, set, date

packages/svelte/src/internal/client/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export const IS_ELSEIF = 1 << 13;
1414
export const EFFECT_RAN = 1 << 14;
1515

1616
export const STATE_SYMBOL = Symbol('$state');
17+
export const INSPECT_SYMBOL = Symbol('$inspect');

packages/svelte/src/internal/client/runtime.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {
2222
BRANCH_EFFECT,
2323
STATE_SYMBOL,
2424
BLOCK_EFFECT,
25-
ROOT_EFFECT
25+
ROOT_EFFECT,
26+
INSPECT_SYMBOL
2627
} from './constants.js';
2728
import { flush_tasks } from './dom/task.js';
2829
import { add_owner } from './dev/ownership.js';
@@ -1096,6 +1097,24 @@ export function pop(component) {
10961097
return component || /** @type {T} */ ({});
10971098
}
10981099

1100+
/**
1101+
*
1102+
* This is called from the inspect.
1103+
* Deeply traverse every item in the array with `deep_read` to register for inspect callback
1104+
* If the item implements INSPECT_SYMBOL, will use that instead
1105+
* @param {Array<any>} value
1106+
* @returns {void}
1107+
*/
1108+
function deep_read_inpect(value) {
1109+
for (const item of value) {
1110+
if (item && typeof item[INSPECT_SYMBOL] === 'function') {
1111+
item[INSPECT_SYMBOL]();
1112+
} else {
1113+
deep_read(item);
1114+
}
1115+
}
1116+
}
1117+
10991118
/**
11001119
* Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`.
11011120
* Does only check first level of an object for performance reasons (heuristic should be good for 99% of all cases).
@@ -1226,7 +1245,7 @@ export function inspect(get_value, inspect = console.log) {
12261245

12271246
inspect_fn = fn;
12281247
const value = get_value();
1229-
deep_read(value);
1248+
deep_read_inpect(value);
12301249
inspect_fn = null;
12311250

12321251
const signals = inspect_captured_signals.slice();

packages/svelte/src/reactivity/date.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
12
import { source, set } from '../internal/client/reactivity/sources.js';
23
import { get } from '../internal/client/runtime.js';
34

@@ -99,4 +100,8 @@ export class ReactiveDate extends Date {
99100
super(...values);
100101
this.#init();
101102
}
103+
104+
[INSPECT_SYMBOL]() {
105+
get(this.#raw_time);
106+
}
102107
}

packages/svelte/src/reactivity/map.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { source, set } from '../internal/client/reactivity/sources.js';
33
import { get } from '../internal/client/runtime.js';
44
import { UNINITIALIZED } from '../constants.js';
55
import { map } from './utils.js';
6+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
67

78
/**
89
* @template K
@@ -12,6 +13,7 @@ export class ReactiveMap extends Map {
1213
/** @type {Map<K, import('#client').Source<V>>} */
1314
#sources = new Map();
1415
#version = source(0);
16+
#inspect_version = source(0);
1517
#size = source(0);
1618

1719
/**
@@ -38,6 +40,9 @@ export class ReactiveMap extends Map {
3840
#increment_version() {
3941
set(this.#version, this.#version.v + 1);
4042
}
43+
#increment_inspect_version() {
44+
set(this.#inspect_version, this.#inspect_version.v + 1);
45+
}
4146

4247
/** @param {K} key */
4348
has(key) {
@@ -92,8 +97,11 @@ export class ReactiveMap extends Map {
9297
sources.set(key, source(value));
9398
set(this.#size, sources.size);
9499
this.#increment_version();
100+
this.#increment_inspect_version();
95101
} else {
102+
const old_version = s.version;
96103
set(s, value);
104+
if (s.version !== old_version) this.#increment_inspect_version();
97105
}
98106

99107
return super.set(key, value);
@@ -109,6 +117,7 @@ export class ReactiveMap extends Map {
109117
set(this.#size, sources.size);
110118
set(s, /** @type {V} */ (UNINITIALIZED));
111119
this.#increment_version();
120+
this.#increment_inspect_version();
112121
}
113122

114123
return super.delete(key);
@@ -123,6 +132,7 @@ export class ReactiveMap extends Map {
123132
set(s, /** @type {V} */ (UNINITIALIZED));
124133
}
125134
this.#increment_version();
135+
this.#increment_inspect_version();
126136
}
127137

128138
sources.clear();
@@ -154,4 +164,8 @@ export class ReactiveMap extends Map {
154164
get size() {
155165
return get(this.#size);
156166
}
167+
168+
[INSPECT_SYMBOL]() {
169+
get(this.#inspect_version);
170+
}
157171
}

packages/svelte/src/reactivity/set.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DEV } from 'esm-env';
22
import { source, set } from '../internal/client/reactivity/sources.js';
33
import { get } from '../internal/client/runtime.js';
44
import { map } from './utils.js';
5+
import { INSPECT_SYMBOL } from '../internal/client/constants.js';
56

67
var read_methods = ['forEach', 'isDisjointFrom', 'isSubsetOf', 'isSupersetOf'];
78
var set_like_methods = ['difference', 'intersection', 'symmetricDifference', 'union'];
@@ -150,4 +151,7 @@ export class ReactiveSet extends Set {
150151
get size() {
151152
return get(this.#size);
152153
}
154+
[INSPECT_SYMBOL]() {
155+
get(this.#version);
156+
}
153157
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { test } from '../../test';
2+
import { flushSync } from 'svelte';
3+
import { log } from './log';
4+
5+
export default test({
6+
compileOptions: {
7+
dev: true
8+
},
9+
before_test() {
10+
log.length = 0;
11+
},
12+
async test({ assert, target }) {
13+
const [in1, in2] = target.querySelectorAll('input');
14+
const [b1, b2, b3] = target.querySelectorAll('button');
15+
16+
assert.deepEqual(log, [
17+
{ label: 'map', type: 'init', values: [] },
18+
{ label: 'set', type: 'init', values: [] },
19+
{ label: 'date', type: 'init', values: 1712966400000 }
20+
]);
21+
log.length = 0;
22+
23+
b1.click(); // map.set('key', 'value')
24+
25+
in1.value = 'name';
26+
in2.value = 'Svelte';
27+
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
28+
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
29+
flushSync(() => b1.click()); // map.set('name', 'Svelte')
30+
31+
in2.value = 'World';
32+
in2.dispatchEvent(new window.Event('input', { bubbles: true }));
33+
flushSync(() => b1.click()); // map.set('name', 'World')
34+
flushSync(() => b1.click()); // map.set('name', 'World')
35+
36+
assert.deepEqual(log, [
37+
{ label: 'map', type: 'update', values: [['key', 'value']] },
38+
{
39+
label: 'map',
40+
type: 'update',
41+
values: [
42+
['key', 'value'],
43+
['name', 'Svelte']
44+
]
45+
},
46+
{
47+
label: 'map',
48+
type: 'update',
49+
values: [
50+
['key', 'value'],
51+
['name', 'World']
52+
]
53+
}
54+
]);
55+
log.length = 0;
56+
57+
b2.click(); // set.add('name');
58+
59+
in1.value = 'Svelte';
60+
in1.dispatchEvent(new window.Event('input', { bubbles: true }));
61+
b2.click(); // set.add('Svelte');
62+
63+
b2.click(); // set.add('Svelte');
64+
65+
assert.deepEqual(log, [
66+
{ label: 'set', type: 'update', values: ['name'] },
67+
{ label: 'set', type: 'update', values: ['name', 'Svelte'] }
68+
]);
69+
log.length = 0;
70+
71+
b3.click(); // date.minutes++
72+
b3.click(); // date.minutes++
73+
b3.click(); // date.minutes++
74+
75+
assert.deepEqual(log, [
76+
{ label: 'date', type: 'update', values: 1712966460000 },
77+
{ label: 'date', type: 'update', values: 1712966520000 },
78+
{ label: 'date', type: 'update', values: 1712966580000 }
79+
]);
80+
}
81+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/** @type {any[]} */
2+
export const log = [];
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script>
2+
import { Map, Set, Date } from 'svelte/reactivity';
3+
import { log } from './log';
4+
5+
const map = new Map();
6+
const set = new Set();
7+
const date = new Date('2024-04-13 00:00:00+0000');
8+
let key = $state('key');
9+
let value = $state('value');
10+
11+
$inspect(map).with((type, map) => {
12+
log.push({ label: 'map', type, values: [...map] });
13+
});
14+
$inspect(set).with((type, set) => {
15+
log.push({ label: 'set', type, values: [...set] });
16+
});
17+
$inspect(date).with((type, date) => {
18+
log.push({ label: 'date', type, values: date.getTime() });
19+
});
20+
</script>
21+
22+
<input bind:value={key} />
23+
<input bind:value={value} />
24+
25+
<button on:click={() => map.set(key, value)}>map</button>
26+
<button on:click={() => set.add(key)}>set</button>
27+
<button on:click={() => date.setMinutes(date.getMinutes() + 1)}>date</button>

0 commit comments

Comments
 (0)