Skip to content

Commit 10980ff

Browse files
committed
feat: support for stores and observables. enable/disable with stores prop. enabled by default.
1 parent a5b87e5 commit 10980ff

21 files changed

+346
-71
lines changed

package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": "0.1.1",
55
"repository": {
66
"type": "git",
7-
"url": "https://github.com/ampled/svelte-inspect-value.git"
7+
"url": "git+https://github.com/ampled/svelte-inspect-value.git"
88
},
99
"homepage": "https://inspect.eirik.space",
1010
"license": "MIT",
@@ -73,6 +73,7 @@
7373
"prettier-plugin-organize-imports": "^4.1.0",
7474
"prettier-plugin-svelte": "^3.3.2",
7575
"publint": "^0.2.0",
76+
"rxjs": "^7.8.2",
7677
"semantic-release": "^24.2.2",
7778
"shiki": "^2.1.0",
7879
"svelte": "^5.19.0",

src/doclib/examples/AllTypes.svelte

+54
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import { browser } from '$app/environment'
33
import Inspect from '$lib/Inspect.svelte'
44
import type { InspectProps } from '$lib/types.js'
5+
import { Observable, interval } from 'rxjs'
56
import { onMount } from 'svelte'
7+
import { readable, writable } from 'svelte/store'
68
import sprite from './media/squirtle.png'
79
import audio from './media/squirtle_cry.ogg'
810
@@ -46,7 +48,52 @@
4648
}
4749
}
4850
51+
function customStore(initialValue = 0) {
52+
let interval: number | undefined
53+
let val = writable(initialValue, () => {
54+
if (browser) {
55+
interval = setInterval(() => {
56+
val.update((n) => n + 1)
57+
}, 500)
58+
}
59+
60+
return () => {
61+
clearInterval(interval)
62+
}
63+
})
64+
65+
return {
66+
...val,
67+
set value(v: number) {
68+
val.set(v)
69+
},
70+
}
71+
}
72+
73+
const o = new Observable((subscriber) => {
74+
subscriber.next(1)
75+
subscriber.next(2)
76+
subscriber.next(3)
77+
setTimeout(() => {
78+
subscriber.next(4)
79+
subscriber.complete()
80+
}, 1000)
81+
})
82+
83+
const fakeStore = {
84+
subscribe: () => {},
85+
}
86+
4987
const allTypes = $state({
88+
stores: {
89+
a: readable('test'),
90+
b: writable({ testing: 'haha' }),
91+
b2: writable({ testing: 'haha' }),
92+
c: customStore(),
93+
o,
94+
interval: interval(1230),
95+
fakeStore,
96+
},
5097
strings: {
5198
basic: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
5299
multilineString: 'check\n\tthis\n\t\tout',
@@ -94,6 +141,12 @@
94141
g: [{ test: 1, a: 2, b: 2 }],
95142
name: 'a',
96143
},
144+
nestedPromises: {
145+
promises: {
146+
a: new Promise(() => {}),
147+
b: Promise.resolve('foo'),
148+
},
149+
},
97150
nestedArrays: [[[[[[[[[[[[[[[[['end']]]]]]]]]]]]]]]]],
98151
},
99152
functions: {
@@ -303,6 +356,7 @@
303356
'spaces in between': ' a ',
304357
},
305358
_: 'underscores are legit',
359+
$: 'dollar signs should be legit',
306360
'asdf\\': 'oo',
307361
[Symbol('')]: 'agaga',
308362
},

src/doclib/examples/MapAndSet.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<p><code>Inspect</code> handles map and set instances.</p>
1212

1313
<Inspect
14-
style="width: 400px"
14+
style="width: 500px"
1515
value={{
1616
map: new Map<unknown, unknown>([
1717
['yeah', 1],

src/doclib/examples/Stores.svelte

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<script lang="ts">
2+
import { browser } from '$app/environment'
3+
import Inspect from '$lib/Inspect.svelte'
4+
import { type InspectOptions, GLOBAL_OPTIONS_CONTEXT } from '$lib/options.svelte.js'
5+
import { fromEvent, map, Observable, startWith } from 'rxjs'
6+
import { getContext, onMount } from 'svelte'
7+
import type { SvelteMap } from 'svelte/reactivity'
8+
import { readable, writable } from 'svelte/store'
9+
import ToggleButton from '../../routes/ToggleButton.svelte'
10+
11+
getContext<SvelteMap<string, string>>('toc')?.set('Stores', 'stores')
12+
const globalInspectOptions = getContext<() => InspectOptions>(GLOBAL_OPTIONS_CONTEXT)()
13+
14+
const setOption =
15+
getContext<(name: keyof InspectOptions, value: unknown) => void>('set-global-option')
16+
17+
function customStore(initialValue = 0) {
18+
let interval: number | undefined
19+
let val = writable(initialValue, () => {
20+
if (browser) {
21+
interval = setInterval(() => {
22+
val.update((n) => n + 1)
23+
}, 500)
24+
}
25+
26+
return () => {
27+
clearInterval(interval)
28+
}
29+
})
30+
31+
return {
32+
...val,
33+
set value(v: number) {
34+
val.set(v)
35+
},
36+
}
37+
}
38+
39+
let eventObservable = $state<Observable<unknown>>()
40+
41+
onMount(() => {
42+
eventObservable = fromEvent(document.body, 'click').pipe(
43+
startWith(`0 clicks`),
44+
map((_, i) => `${i} clicks`)
45+
)
46+
let subscription = eventObservable.subscribe()
47+
48+
const st = writable(0)
49+
50+
let s = st.subscribe(() => {})
51+
52+
return () => {
53+
subscription.unsubscribe()
54+
s()
55+
}
56+
})
57+
</script>
58+
59+
<div class="flex col">
60+
<h3 id="stores">Stores</h3>
61+
<p>
62+
Objects with a <code>subscribe</code> function are recognized as stores or observables. The
63+
stores will be subscribed to and the latest emitted value will be displayed.<br />
64+
If the store does not return a valid
65+
<dfn title="function or object with unsubscribe-function">unsubscriber</dfn> or the subscribe-function
66+
throws an error the store will be displayed as a regular object using the default object-view.
67+
</p>
68+
69+
<Inspect
70+
value={{
71+
writableStore: writable('i am the store value'),
72+
readableStore: readable({ a: { b: { c: { d: { e: 'end' } } } } }),
73+
customStore: customStore(0),
74+
clickObservable: eventObservable,
75+
fakeStore: {
76+
subscribe: () => 'hi',
77+
},
78+
}}
79+
name="stores"
80+
/>
81+
82+
<p>
83+
This can be enabled / disabled with the <code>stores</code>-prop:
84+
<span style="margin-left: 0.5em;">
85+
<ToggleButton
86+
bind:checked={() => globalInspectOptions.stores, (val) => setOption('stores', val)}
87+
>
88+
stores
89+
</ToggleButton>
90+
</span>
91+
</p>
92+
</div>

src/lib/Root.svelte

+1-13
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
); /* e.g. copy to clipboard */
146146
--_promise-pending-color: var(--promise-pending-color, var(--base0A));
147147
--_promise-fulfilled-color: var(--promise-fulfilled-color, var(--base0B));
148-
--_promise-rejected-color: var(--promise-rejected-color, var(--base0E));
148+
--_promise-rejected-color: var(--promise-rejected-color, var(--base08));
149149
--_promise-bracket-color: var(--promise-bracket-color, var(--base03));
150150
151151
/* key */
@@ -465,18 +465,6 @@
465465
&.html {
466466
padding-inline: 0.5em;
467467
}
468-
469-
&.promise {
470-
&.rejected {
471-
color: var(--_promise-rejected-color);
472-
}
473-
&.pending {
474-
color: var(--_promise-pending-color);
475-
}
476-
&.fulfilled {
477-
color: var(--_promise-fulfilled-color);
478-
}
479-
}
480468
}
481469
482470
.svelte-inspect-value :global(a) {

src/lib/components/HTMLViewSimple.svelte

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
5757
let attrs = $derived.by(() => {
5858
if (element.ele) {
59-
return Object.entries(element.ele.attributes)
59+
return Object.entries(element.ele.attributes ?? {})
6060
.map(([, attr]) => [attr.name, attr.value])
6161
.filter(([name]) => !['class', 'style', 'data'].includes(name) && !name.startsWith('data-'))
6262
}
@@ -82,9 +82,9 @@
8282
let entries = $derived(
8383
Object.entries({
8484
...Object.fromEntries(attrs),
85-
class: element.ele.className.split(' ').filter(Boolean),
85+
class: element.ele.className?.split(' ').filter(Boolean) ?? [],
8686
styles: Object.fromEntries(styles),
87-
data: Object.fromEntries(Object.entries(element.ele.dataset)),
87+
data: Object.fromEntries(Object.entries(element.ele.dataset ?? {})),
8888
...current,
8989
children: value.children,
9090
}).filter(([, v]) =>

src/lib/components/Node.svelte

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<script lang="ts">
22
// eslint-disable @typescript-eslint/no-explicit-any
33
import type { Component } from 'svelte'
4-
import { useOptions } from '../options.svelte.js'
5-
import { InspectError, type CustomComponents, type TypeViewProps } from '../types.js'
4+
import { useOptions, type InspectOptions } from '../options.svelte.js'
5+
import { InspectError, type TypeViewProps } from '../types.js'
66
import { getType, type ValueType } from '../util.js'
77
import Default from './Default.svelte'
88
import HtmlView from './HTMLView.svelte'
@@ -22,15 +22,17 @@
2222
2323
const options = useOptions()
2424
25-
let type: ValueType = $derived(getType(value))
25+
let type: ValueType = $derived(getType(value, options.value.stores))
2626
27+
// FIXME: this is so messy
2728
function getTypeComponent(
29+
value: unknown,
2830
type: ValueType,
29-
custom: CustomComponents,
30-
useDefaults: boolean
31+
useDefaults: boolean,
32+
options: InspectOptions
3133
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3234
): [Component<TypeViewProps<any>>, Partial<TypeViewProps>] {
33-
let entry = getComponent(type, useDefaults ? {} : custom)
35+
let entry = getComponent(type, useDefaults ? {} : options.customComponents)
3436
3537
if (entry) {
3638
let [component, propfn, predicate] = entry
@@ -53,7 +55,7 @@
5355
}
5456
5557
let [TypeComponent, componentProps] = $derived(
56-
getTypeComponent(type, options.value.customComponents, usedefaults)
58+
getTypeComponent(value, type, usedefaults, options.value)
5759
)
5860
let path = $derived(key != null && prevPath ? [...prevPath, key] : ['root'])
5961
</script>

0 commit comments

Comments
 (0)