nuxt-devtools-observatory 0.1.11 → 0.1.13
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/LICENSE +9 -0
- package/README.md +114 -31
- package/client/dist/assets/index-BFrWlkvI.js +17 -0
- package/client/dist/assets/index-BUQNNbrq.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/src/stores/observatory.ts +59 -15
- package/client/src/views/ComposableTracker.vue +685 -101
- package/client/src/views/ProvideInjectGraph.vue +232 -61
- package/client/src/views/RenderHeatmap.vue +138 -37
- package/client/src/views/TransitionTimeline.vue +2 -2
- package/client/src/views/ValueInspector.vue +124 -0
- package/dist/module.json +1 -1
- package/dist/runtime/composables/composable-registry.d.ts +21 -0
- package/dist/runtime/composables/composable-registry.js +208 -53
- package/dist/runtime/composables/provide-inject-registry.d.ts +13 -1
- package/dist/runtime/composables/provide-inject-registry.js +34 -6
- package/dist/runtime/composables/render-registry.d.ts +18 -8
- package/dist/runtime/composables/render-registry.js +29 -35
- package/dist/runtime/composables/transition-registry.js +13 -7
- package/dist/runtime/plugin.js +49 -17
- package/package.json +7 -3
- package/client/dist/assets/index--Igqz_EM.js +0 -17
- package/client/dist/assets/index-BoC4M4Nb.css +0 -1
|
@@ -9,13 +9,15 @@ interface ComponentNode {
|
|
|
9
9
|
element?: string
|
|
10
10
|
depth: number
|
|
11
11
|
path: string[]
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
rerenders: number
|
|
13
|
+
mountCount: number
|
|
14
14
|
avgMs: number
|
|
15
15
|
triggers: string[]
|
|
16
16
|
children: ComponentNode[]
|
|
17
17
|
parentId?: string
|
|
18
18
|
parentLabel?: string
|
|
19
|
+
isPersistent: boolean
|
|
20
|
+
isHydrationMount: boolean
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const TreeNode = defineComponent({
|
|
@@ -30,7 +32,7 @@ const TreeNode = defineComponent({
|
|
|
30
32
|
emits: ['select', 'toggle'],
|
|
31
33
|
setup(props, { emit }): () => VNode | null {
|
|
32
34
|
function nodeValue(node: ComponentNode) {
|
|
33
|
-
return props.mode === 'count' ? node.
|
|
35
|
+
return props.mode === 'count' ? node.rerenders + node.mountCount : node.avgMs
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function isHot(node: ComponentNode) {
|
|
@@ -48,7 +50,7 @@ const TreeNode = defineComponent({
|
|
|
48
50
|
const node = props.node!
|
|
49
51
|
const expanded = props.expandedIds?.has(node.id) ?? false
|
|
50
52
|
const canExpand = node.children.length > 0
|
|
51
|
-
const metric = props.mode === 'count' ? `${node.
|
|
53
|
+
const metric = props.mode === 'count' ? `${node.rerenders + node.mountCount}` : `${node.avgMs.toFixed(1)}ms`
|
|
52
54
|
const metricLabel = props.mode === 'count' ? 'renders' : 'avg'
|
|
53
55
|
const badges = []
|
|
54
56
|
const normalizedElement = node.element?.toLowerCase()
|
|
@@ -58,7 +60,11 @@ const TreeNode = defineComponent({
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
if (node.file !== 'unknown') {
|
|
61
|
-
const fileBadge =
|
|
63
|
+
const fileBadge =
|
|
64
|
+
node.file
|
|
65
|
+
.split('/')
|
|
66
|
+
.pop()
|
|
67
|
+
?.replace(/\.vue$/i, '') ?? node.file
|
|
62
68
|
|
|
63
69
|
if (fileBadge !== node.label && !badges.includes(fileBadge)) {
|
|
64
70
|
badges.push(fileBadge)
|
|
@@ -104,7 +110,23 @@ const TreeNode = defineComponent({
|
|
|
104
110
|
: null,
|
|
105
111
|
]),
|
|
106
112
|
h('div', { class: 'tree-metrics mono' }, [
|
|
107
|
-
node.
|
|
113
|
+
node.isPersistent
|
|
114
|
+
? h(
|
|
115
|
+
'span',
|
|
116
|
+
{ class: 'tree-persistent-pill', title: 'Layout / persistent component — survives navigation' },
|
|
117
|
+
'persistent'
|
|
118
|
+
)
|
|
119
|
+
: null,
|
|
120
|
+
node.isHydrationMount
|
|
121
|
+
? h(
|
|
122
|
+
'span',
|
|
123
|
+
{
|
|
124
|
+
class: 'tree-hydration-pill',
|
|
125
|
+
title: 'First mount was SSR hydration — not a user-triggered render',
|
|
126
|
+
},
|
|
127
|
+
'hydrated'
|
|
128
|
+
)
|
|
129
|
+
: null,
|
|
108
130
|
h('span', { class: 'tree-metric-pill' }, `${metric} ${metricLabel}`),
|
|
109
131
|
]),
|
|
110
132
|
]
|
|
@@ -134,7 +156,19 @@ const TreeNode = defineComponent({
|
|
|
134
156
|
const { renders, connected } = useObservatoryData()
|
|
135
157
|
|
|
136
158
|
const activeMode = ref<'count' | 'time'>('count')
|
|
137
|
-
|
|
159
|
+
// Separate thresholds per mode so switching modes doesn't produce nonsense results.
|
|
160
|
+
// Count: flag components that rendered 3+ times (1 hydration mount is normal).
|
|
161
|
+
// Time: flag components averaging 16ms+ (one animation frame budget).
|
|
162
|
+
const countThreshold = ref(3)
|
|
163
|
+
const timeThreshold = ref(16)
|
|
164
|
+
// Writable computed so the threshold slider can use v-model directly.
|
|
165
|
+
const activeThreshold = computed({
|
|
166
|
+
get: () => (activeMode.value === 'count' ? countThreshold.value : timeThreshold.value),
|
|
167
|
+
set: (val: number) => {
|
|
168
|
+
if (activeMode.value === 'count') countThreshold.value = val
|
|
169
|
+
else timeThreshold.value = val
|
|
170
|
+
},
|
|
171
|
+
})
|
|
138
172
|
const activeHotOnly = ref(false)
|
|
139
173
|
const frozen = ref(false)
|
|
140
174
|
const search = ref('')
|
|
@@ -153,7 +187,10 @@ function displayLabel(entry: RenderEntry) {
|
|
|
153
187
|
return entry.element
|
|
154
188
|
}
|
|
155
189
|
|
|
156
|
-
const basename = entry.file
|
|
190
|
+
const basename = entry.file
|
|
191
|
+
.split('/')
|
|
192
|
+
.pop()
|
|
193
|
+
?.replace(/\.vue$/i, '')
|
|
157
194
|
|
|
158
195
|
if (basename && basename !== 'unknown') {
|
|
159
196
|
return basename
|
|
@@ -181,12 +218,14 @@ function buildNodes(entries: RenderEntry[]) {
|
|
|
181
218
|
element: entry.element,
|
|
182
219
|
depth: 0,
|
|
183
220
|
path: [],
|
|
184
|
-
|
|
185
|
-
|
|
221
|
+
rerenders: entry.rerenders ?? 0,
|
|
222
|
+
mountCount: entry.mountCount ?? 1,
|
|
186
223
|
avgMs: entry.avgMs,
|
|
187
224
|
triggers: entry.triggers.map(formatTrigger),
|
|
188
225
|
children: [],
|
|
189
226
|
parentId: entry.parentUid !== undefined ? String(entry.parentUid) : undefined,
|
|
227
|
+
isPersistent: Boolean(entry.isPersistent),
|
|
228
|
+
isHydrationMount: Boolean(entry.isHydrationMount),
|
|
190
229
|
})
|
|
191
230
|
}
|
|
192
231
|
|
|
@@ -282,7 +321,17 @@ function defaultExpandedIds(root: ComponentNode | null) {
|
|
|
282
321
|
return new Set<string>()
|
|
283
322
|
}
|
|
284
323
|
|
|
285
|
-
|
|
324
|
+
// Expand all nodes that have children — gives a fully-open tree on first load.
|
|
325
|
+
// The user can collapse individual branches as needed.
|
|
326
|
+
const expanded = new Set<string>()
|
|
327
|
+
function expandAll(node: ComponentNode) {
|
|
328
|
+
if (node.children.length > 0) {
|
|
329
|
+
expanded.add(node.id)
|
|
330
|
+
node.children.forEach(expandAll)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
expandAll(root)
|
|
334
|
+
return expanded
|
|
286
335
|
}
|
|
287
336
|
|
|
288
337
|
function searchExpandedIds(root: ComponentNode | null, term: string) {
|
|
@@ -309,7 +358,7 @@ function searchExpandedIds(root: ComponentNode | null, term: string) {
|
|
|
309
358
|
}
|
|
310
359
|
|
|
311
360
|
function nodeValue(node: ComponentNode) {
|
|
312
|
-
return activeMode.value === 'count' ? node.
|
|
361
|
+
return activeMode.value === 'count' ? node.rerenders + node.mountCount : node.avgMs
|
|
313
362
|
}
|
|
314
363
|
|
|
315
364
|
function isHot(node: ComponentNode) {
|
|
@@ -411,8 +460,7 @@ const appEntries = computed(() =>
|
|
|
411
460
|
)
|
|
412
461
|
|
|
413
462
|
const activeSelected = computed(() => allComponents.value.find((node) => node.id === activeSelectedId.value) ?? null)
|
|
414
|
-
const totalRenders = computed(() => allComponents.value.reduce((sum, node) => sum + node.
|
|
415
|
-
const totalNavigationRenders = computed(() => allComponents.value.reduce((sum, node) => sum + (Number.isFinite(node.navigationRenders) ? node.navigationRenders : 0), 0))
|
|
463
|
+
const totalRenders = computed(() => allComponents.value.reduce((sum, node) => sum + node.rerenders + node.mountCount, 0))
|
|
416
464
|
const hotCount = computed(() => allComponents.value.filter((node) => isHot(node)).length)
|
|
417
465
|
const avgTime = computed(() => {
|
|
418
466
|
const components = allComponents.value.filter((node) => node.avgMs > 0)
|
|
@@ -561,7 +609,12 @@ function toggleFreeze() {
|
|
|
561
609
|
}
|
|
562
610
|
|
|
563
611
|
function basename(file: string) {
|
|
564
|
-
return
|
|
612
|
+
return (
|
|
613
|
+
file
|
|
614
|
+
.split('/')
|
|
615
|
+
.pop()
|
|
616
|
+
?.replace(/\.vue$/i, '') ?? file
|
|
617
|
+
)
|
|
565
618
|
}
|
|
566
619
|
|
|
567
620
|
function pathLabel(node: ComponentNode) {
|
|
@@ -578,8 +631,15 @@ function pathLabel(node: ComponentNode) {
|
|
|
578
631
|
</div>
|
|
579
632
|
<div class="threshold-group">
|
|
580
633
|
<span class="muted text-sm">threshold</span>
|
|
581
|
-
<input
|
|
582
|
-
|
|
634
|
+
<input
|
|
635
|
+
v-model.number="activeThreshold"
|
|
636
|
+
type="range"
|
|
637
|
+
:min="activeMode === 'count' ? 2 : 4"
|
|
638
|
+
:max="activeMode === 'count' ? 20 : 100"
|
|
639
|
+
:step="activeMode === 'count' ? 1 : 4"
|
|
640
|
+
style="width: 90px"
|
|
641
|
+
/>
|
|
642
|
+
<span class="mono text-sm">{{ activeThreshold }}{{ activeMode === 'count' ? '+ renders' : 'ms+' }}</span>
|
|
583
643
|
</div>
|
|
584
644
|
<button :class="{ active: activeHotOnly }" @click="activeHotOnly = !activeHotOnly">hot only</button>
|
|
585
645
|
<button :class="{ active: frozen }" style="margin-left: auto" @click="toggleFreeze">
|
|
@@ -595,7 +655,6 @@ function pathLabel(node: ComponentNode) {
|
|
|
595
655
|
<div class="stat-card">
|
|
596
656
|
<div class="stat-label">total renders</div>
|
|
597
657
|
<div class="stat-val">{{ totalRenders }}</div>
|
|
598
|
-
<div class="stat-sub mono">{{ totalNavigationRenders }} nav</div>
|
|
599
658
|
</div>
|
|
600
659
|
<div class="stat-card">
|
|
601
660
|
<div class="stat-label">hot</div>
|
|
@@ -659,10 +718,23 @@ function pathLabel(node: ComponentNode) {
|
|
|
659
718
|
</div>
|
|
660
719
|
|
|
661
720
|
<div class="detail-pill-row">
|
|
662
|
-
<span class="detail-pill mono">
|
|
663
|
-
|
|
721
|
+
<span class="detail-pill mono">
|
|
722
|
+
{{ activeSelected.rerenders + activeSelected.mountCount }} render{{
|
|
723
|
+
activeSelected.rerenders + activeSelected.mountCount !== 1 ? 's' : ''
|
|
724
|
+
}}
|
|
725
|
+
</span>
|
|
726
|
+
<span class="detail-pill mono muted">
|
|
727
|
+
{{ activeSelected.mountCount }} mount{{ activeSelected.mountCount !== 1 ? 's' : '' }}
|
|
728
|
+
</span>
|
|
729
|
+
<span v-if="activeSelected.rerenders" class="detail-pill mono">
|
|
730
|
+
{{ activeSelected.rerenders }} re-render{{ activeSelected.rerenders !== 1 ? 's' : '' }}
|
|
731
|
+
</span>
|
|
732
|
+
<span v-if="activeSelected.isPersistent" class="detail-pill mono persistent">persistent</span>
|
|
733
|
+
<span v-if="activeSelected.isHydrationMount" class="detail-pill mono hydrated">hydrated</span>
|
|
664
734
|
<span class="detail-pill mono">{{ activeSelected.avgMs.toFixed(1) }}ms avg</span>
|
|
665
|
-
<span class="detail-pill mono" :class="{ hot: isHot(activeSelected) }">
|
|
735
|
+
<span class="detail-pill mono" :class="{ hot: isHot(activeSelected) }">
|
|
736
|
+
{{ isHot(activeSelected) ? 'hot' : 'cool' }}
|
|
737
|
+
</span>
|
|
666
738
|
</div>
|
|
667
739
|
|
|
668
740
|
<div class="section-label">identity</div>
|
|
@@ -683,14 +755,24 @@ function pathLabel(node: ComponentNode) {
|
|
|
683
755
|
|
|
684
756
|
<div class="section-label">rendering</div>
|
|
685
757
|
<div class="meta-grid">
|
|
686
|
-
<span class="muted text-sm">
|
|
687
|
-
<span class="mono text-sm">{{
|
|
688
|
-
<span class="muted text-sm">
|
|
689
|
-
<span class="mono text-sm">{{ activeSelected.
|
|
758
|
+
<span class="muted text-sm">total renders</span>
|
|
759
|
+
<span class="mono text-sm">{{ activeSelected.rerenders + activeSelected.mountCount }}</span>
|
|
760
|
+
<span class="muted text-sm">re-renders</span>
|
|
761
|
+
<span class="mono text-sm">{{ activeSelected.rerenders }}</span>
|
|
762
|
+
<span class="muted text-sm">mounts</span>
|
|
763
|
+
<span class="mono text-sm">{{ activeSelected.mountCount }}</span>
|
|
764
|
+
<span class="muted text-sm">persistent</span>
|
|
765
|
+
<span class="mono text-sm" :style="{ color: activeSelected.isPersistent ? 'var(--amber)' : 'inherit' }">
|
|
766
|
+
{{ activeSelected.isPersistent ? 'yes — survives navigation' : 'no' }}
|
|
767
|
+
</span>
|
|
768
|
+
<span class="muted text-sm">hydration mount</span>
|
|
769
|
+
<span class="mono text-sm">{{ activeSelected.isHydrationMount ? 'yes — SSR hydrated' : 'no' }}</span>
|
|
770
|
+
<span class="muted text-sm">avg render time</span>
|
|
771
|
+
<span class="mono text-sm">{{ activeSelected.avgMs.toFixed(1) }}ms</span>
|
|
690
772
|
<span class="muted text-sm">threshold</span>
|
|
691
|
-
<span class="mono text-sm">{{ activeThreshold }}</span>
|
|
692
|
-
<span class="muted text-sm">
|
|
693
|
-
<span class="mono text-sm">{{ activeMode === 'count' ? 'render count' : 'render time' }}</span>
|
|
773
|
+
<span class="mono text-sm">{{ activeThreshold }}{{ activeMode === 'count' ? '+ renders' : 'ms+' }}</span>
|
|
774
|
+
<span class="muted text-sm">mode</span>
|
|
775
|
+
<span class="mono text-sm">{{ activeMode === 'count' ? 're-render count' : 'render time' }}</span>
|
|
694
776
|
</div>
|
|
695
777
|
|
|
696
778
|
<div class="section-label">triggers</div>
|
|
@@ -970,17 +1052,26 @@ function pathLabel(node: ComponentNode) {
|
|
|
970
1052
|
color: var(--text3);
|
|
971
1053
|
}
|
|
972
1054
|
|
|
973
|
-
:deep(.tree-
|
|
1055
|
+
:deep(.tree-persistent-pill) {
|
|
974
1056
|
display: inline-flex;
|
|
975
1057
|
align-items: center;
|
|
976
|
-
justify-content: center;
|
|
977
|
-
min-width: 54px;
|
|
978
1058
|
padding: 2px 8px;
|
|
979
|
-
border: 1px solid color-mix(in srgb, var(--
|
|
1059
|
+
border: 1px solid color-mix(in srgb, var(--amber) 55%, var(--border));
|
|
980
1060
|
border-radius: 999px;
|
|
981
|
-
background: color-mix(in srgb, var(--
|
|
1061
|
+
background: color-mix(in srgb, var(--amber) 10%, var(--bg2));
|
|
982
1062
|
font-size: 10px;
|
|
983
|
-
color: color-mix(in srgb, var(--
|
|
1063
|
+
color: color-mix(in srgb, var(--amber) 80%, var(--text));
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
:deep(.tree-hydration-pill) {
|
|
1067
|
+
display: inline-flex;
|
|
1068
|
+
align-items: center;
|
|
1069
|
+
padding: 2px 8px;
|
|
1070
|
+
border: 1px solid color-mix(in srgb, var(--teal) 55%, var(--border));
|
|
1071
|
+
border-radius: 999px;
|
|
1072
|
+
background: color-mix(in srgb, var(--teal) 10%, var(--bg2));
|
|
1073
|
+
font-size: 10px;
|
|
1074
|
+
color: color-mix(in srgb, var(--teal) 80%, var(--text));
|
|
984
1075
|
}
|
|
985
1076
|
|
|
986
1077
|
:deep(.tree-children) {
|
|
@@ -1029,9 +1120,19 @@ function pathLabel(node: ComponentNode) {
|
|
|
1029
1120
|
color: var(--red);
|
|
1030
1121
|
}
|
|
1031
1122
|
|
|
1032
|
-
.detail-pill.
|
|
1033
|
-
border-color: color-mix(in srgb, var(--
|
|
1034
|
-
color: color-mix(in srgb, var(--
|
|
1123
|
+
.detail-pill.persistent {
|
|
1124
|
+
border-color: color-mix(in srgb, var(--amber) 55%, var(--border));
|
|
1125
|
+
color: color-mix(in srgb, var(--amber) 80%, var(--text));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
.detail-pill.hydrated {
|
|
1129
|
+
border-color: color-mix(in srgb, var(--teal) 55%, var(--border));
|
|
1130
|
+
color: color-mix(in srgb, var(--teal) 80%, var(--text));
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.detail-pill.muted {
|
|
1134
|
+
color: var(--text3);
|
|
1135
|
+
border-color: var(--border);
|
|
1035
1136
|
}
|
|
1036
1137
|
|
|
1037
1138
|
.section-label {
|
|
@@ -56,8 +56,8 @@ const timelineGeometry = computed(() => {
|
|
|
56
56
|
return []
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const minT =
|
|
60
|
-
const maxT =
|
|
59
|
+
const minT = all.reduce((min, e) => Math.min(min, e.startTime), all[0].startTime)
|
|
60
|
+
const maxT = all.reduce((max, e) => Math.max(max, e.endTime ?? e.startTime + 400), 0)
|
|
61
61
|
const span = Math.max(maxT - minT, 1)
|
|
62
62
|
|
|
63
63
|
return all.map((e) => ({
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
value: unknown
|
|
6
|
+
compact?: boolean
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const open = ref(false)
|
|
10
|
+
|
|
11
|
+
function truncate(s: string, max: number) {
|
|
12
|
+
return s.length > max ? s.slice(0, max) + '…' : s
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<span class="vi">
|
|
18
|
+
<!-- null / undefined -->
|
|
19
|
+
<span v-if="value === null || value === undefined" class="vi-null">{{ String(value) }}</span>
|
|
20
|
+
|
|
21
|
+
<!-- boolean -->
|
|
22
|
+
<span v-else-if="typeof value === 'boolean'" class="vi-bool">{{ value }}</span>
|
|
23
|
+
|
|
24
|
+
<!-- number -->
|
|
25
|
+
<span v-else-if="typeof value === 'number'" class="vi-num">{{ value }}</span>
|
|
26
|
+
|
|
27
|
+
<!-- string -->
|
|
28
|
+
<span v-else-if="typeof value === 'string'" class="vi-str">"{{ compact ? truncate(value, 40) : value }}"</span>
|
|
29
|
+
|
|
30
|
+
<!-- array -->
|
|
31
|
+
<span v-else-if="Array.isArray(value)">
|
|
32
|
+
<span v-if="compact || !open">
|
|
33
|
+
<span class="vi-punc">[</span>
|
|
34
|
+
<span class="vi-dim">{{ value.length }} item{{ value.length !== 1 ? 's' : '' }}</span>
|
|
35
|
+
<span class="vi-punc">]</span>
|
|
36
|
+
<button v-if="!compact" class="vi-toggle" @click.stop="open = true">▸</button>
|
|
37
|
+
</span>
|
|
38
|
+
<span v-else class="vi-block">
|
|
39
|
+
<button class="vi-toggle" @click.stop="open = false">▾</button>
|
|
40
|
+
<span class="vi-punc">[</span>
|
|
41
|
+
<span v-for="(item, i) in value" :key="i" class="vi-indent">
|
|
42
|
+
<ValueInspector :value="item" />
|
|
43
|
+
<span v-if="i < value.length - 1" class="vi-punc">,</span>
|
|
44
|
+
</span>
|
|
45
|
+
<span class="vi-punc">]</span>
|
|
46
|
+
</span>
|
|
47
|
+
</span>
|
|
48
|
+
|
|
49
|
+
<!-- object -->
|
|
50
|
+
<span v-else-if="typeof value === 'object'">
|
|
51
|
+
<span v-if="compact || !open">
|
|
52
|
+
<span class="vi-punc">{</span>
|
|
53
|
+
<span class="vi-dim">
|
|
54
|
+
{{
|
|
55
|
+
Object.keys(value as object)
|
|
56
|
+
.slice(0, 3)
|
|
57
|
+
.join(', ')
|
|
58
|
+
}}{{ Object.keys(value as object).length > 3 ? '…' : '' }}
|
|
59
|
+
</span>
|
|
60
|
+
<span class="vi-punc">}</span>
|
|
61
|
+
<button v-if="!compact" class="vi-toggle" @click.stop="open = true">▸</button>
|
|
62
|
+
</span>
|
|
63
|
+
<span v-else class="vi-block">
|
|
64
|
+
<button class="vi-toggle" @click.stop="open = false">▾</button>
|
|
65
|
+
<span class="vi-punc">{</span>
|
|
66
|
+
<span v-for="(v, k, i) in value as Record<string, unknown>" :key="k" class="vi-indent">
|
|
67
|
+
<span class="vi-key">{{ k }}</span>
|
|
68
|
+
<span class="vi-punc">:</span>
|
|
69
|
+
<ValueInspector :value="v" />
|
|
70
|
+
<span v-if="i < Object.keys(value as object).length - 1" class="vi-punc">,</span>
|
|
71
|
+
</span>
|
|
72
|
+
<span class="vi-punc">}</span>
|
|
73
|
+
</span>
|
|
74
|
+
</span>
|
|
75
|
+
|
|
76
|
+
<!-- fallback -->
|
|
77
|
+
<span v-else class="vi-dim">{{ String(value) }}</span>
|
|
78
|
+
</span>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style scoped>
|
|
82
|
+
.vi {
|
|
83
|
+
font-family: var(--font-mono);
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
}
|
|
86
|
+
.vi-null {
|
|
87
|
+
color: #888;
|
|
88
|
+
}
|
|
89
|
+
.vi-bool {
|
|
90
|
+
color: #534ab7;
|
|
91
|
+
}
|
|
92
|
+
.vi-num {
|
|
93
|
+
color: #ba7517;
|
|
94
|
+
}
|
|
95
|
+
.vi-str {
|
|
96
|
+
color: #0f6e56;
|
|
97
|
+
}
|
|
98
|
+
.vi-key {
|
|
99
|
+
color: #185195;
|
|
100
|
+
}
|
|
101
|
+
.vi-punc {
|
|
102
|
+
color: #888;
|
|
103
|
+
}
|
|
104
|
+
.vi-dim {
|
|
105
|
+
color: #888;
|
|
106
|
+
font-style: italic;
|
|
107
|
+
}
|
|
108
|
+
.vi-toggle {
|
|
109
|
+
background: none;
|
|
110
|
+
border: none;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
font-size: 10px;
|
|
113
|
+
color: #888;
|
|
114
|
+
padding: 0 2px;
|
|
115
|
+
}
|
|
116
|
+
.vi-block {
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
}
|
|
120
|
+
.vi-indent {
|
|
121
|
+
padding-left: 12px;
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
</style>
|
package/dist/module.json
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface RefChangeEvent {
|
|
2
|
+
t: number;
|
|
3
|
+
key: string;
|
|
4
|
+
value: unknown;
|
|
5
|
+
}
|
|
1
6
|
export interface ComposableEntry {
|
|
2
7
|
id: string;
|
|
3
8
|
name: string;
|
|
@@ -10,6 +15,13 @@ export interface ComposableEntry {
|
|
|
10
15
|
type: 'ref' | 'computed' | 'reactive';
|
|
11
16
|
value: unknown;
|
|
12
17
|
}>;
|
|
18
|
+
/** Capped at MAX_HISTORY_PER_ENTRY events, newest last */
|
|
19
|
+
history: RefChangeEvent[];
|
|
20
|
+
/**
|
|
21
|
+
* Keys whose underlying ref/reactive object is shared across multiple
|
|
22
|
+
* instances of this composable — indicates module-level (global) state.
|
|
23
|
+
*/
|
|
24
|
+
sharedKeys: string[];
|
|
13
25
|
watcherCount: number;
|
|
14
26
|
intervalCount: number;
|
|
15
27
|
lifecycle: {
|
|
@@ -20,6 +32,8 @@ export interface ComposableEntry {
|
|
|
20
32
|
};
|
|
21
33
|
file: string;
|
|
22
34
|
line: number;
|
|
35
|
+
/** Route path the composable was registered on, e.g. "/products". */
|
|
36
|
+
route: string;
|
|
23
37
|
}
|
|
24
38
|
/**
|
|
25
39
|
* Registers a new composable entry, updates an existing one, or retrieves all entries.
|
|
@@ -31,8 +45,15 @@ export interface ComposableEntry {
|
|
|
31
45
|
*/
|
|
32
46
|
export declare function setupComposableRegistry(): {
|
|
33
47
|
register: (entry: ComposableEntry) => void;
|
|
48
|
+
registerLiveRefs: (id: string, refs: Record<string, import("vue").Ref<unknown>>) => void;
|
|
49
|
+
registerRawRefs: (id: string, refs: Record<string, unknown>) => void;
|
|
50
|
+
onComposableChange: (cb: () => void) => void;
|
|
51
|
+
clear: () => void;
|
|
52
|
+
setRoute: (path: string) => void;
|
|
53
|
+
getRoute: () => string;
|
|
34
54
|
update: (id: string, patch: Partial<ComposableEntry>) => void;
|
|
35
55
|
getAll: () => ComposableEntry[];
|
|
56
|
+
editValue: (id: string, key: string, value: unknown) => void;
|
|
36
57
|
};
|
|
37
58
|
export declare function __trackComposable<T>(name: string, callFn: () => T, meta: {
|
|
38
59
|
file: string;
|