@humanspeak/svelte-json-view-lite 0.2.0 → 0.2.2
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/README.md +1 -1
- package/dist/ExpandableObject.svelte +3 -6
- package/dist/JsonPrimitiveValue.svelte +100 -93
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -266,7 +266,7 @@ Part of the [Humanspeak](https://humanspeak.com) family of runes-native Svelte 5
|
|
|
266
266
|
| [@humanspeak/svelte-virtual-list](https://virtuallist.svelte.page) | Virtual scrolling for Svelte |
|
|
267
267
|
| [@humanspeak/svelte-motion](https://motion.svelte.page) | Framer Motion for Svelte 5 |
|
|
268
268
|
| [@humanspeak/svelte-headless-table](https://table.svelte.page) | Headless data tables for Svelte |
|
|
269
|
-
| [@humanspeak/svelte-diff
|
|
269
|
+
| [@humanspeak/svelte-diff](https://diff.svelte.page) | Diff comparison for Svelte |
|
|
270
270
|
| [@humanspeak/svelte-purify](https://purify.svelte.page) | HTML sanitisation for Svelte |
|
|
271
271
|
| [@humanspeak/svelte-virtual-chat](https://virtualchat.svelte.page) | Virtual chat viewport for Svelte 5 |
|
|
272
272
|
| [@humanspeak/memory-cache](https://memory.svelte.page) | In-memory cache for TypeScript |
|
|
@@ -78,8 +78,6 @@
|
|
|
78
78
|
const ariaLabel = $derived(
|
|
79
79
|
expanded ? activeAriaLabels.collapseJson : activeAriaLabels.expandJson
|
|
80
80
|
)
|
|
81
|
-
const childLevel = $derived(level + 1)
|
|
82
|
-
const hasField = $derived(field !== undefined)
|
|
83
81
|
const labelText = $derived(quoteString(field ?? '', style.quotesForFieldNames))
|
|
84
82
|
|
|
85
83
|
// Object keys resolved once and shared by the count and the tuple builder,
|
|
@@ -90,7 +88,6 @@
|
|
|
90
88
|
// Child *count* is cheap — array length, or the key count for objects.
|
|
91
89
|
// Neither touches child *values*, so a collapsed node stays allocation-free.
|
|
92
90
|
const count = $derived(objectKeys ? objectKeys.length : (value as unknown[]).length)
|
|
93
|
-
const lastIndex = $derived(count - 1)
|
|
94
91
|
|
|
95
92
|
// The tuple array is materialized lazily on first open, then kept: a node
|
|
96
93
|
// that is never expanded never allocates its N tuples or reads its N child
|
|
@@ -147,7 +144,7 @@
|
|
|
147
144
|
it tight anyway for a consistent rule.
|
|
148
145
|
-->
|
|
149
146
|
<!-- prettier-ignore -->
|
|
150
|
-
<span bind:this={expanderButton} use:registerExpander class={expanderIconStyle} role="button" aria-label={ariaLabel} aria-expanded={expanded} aria-controls={expanded ? contentsId : undefined} tabindex={level === 0 ? 0 : -1} onclick={onClick} onkeydown={onKeyDown}></span>{#if
|
|
147
|
+
<span bind:this={expanderButton} use:registerExpander class={expanderIconStyle} role="button" aria-label={ariaLabel} aria-expanded={expanded} aria-controls={expanded ? contentsId : undefined} tabindex={level === 0 ? 0 : -1} onclick={onClick} onkeydown={onKeyDown}></span>{#if field !== undefined}{#if snippets.label}{@render snippets.label(
|
|
151
148
|
{ field: field ?? '', level }
|
|
152
149
|
)}{:else if clickToExpandNode}<!-- svelte-ignore a11y_no_static_element_interactions --><span
|
|
153
150
|
class={style.clickableLabel}
|
|
@@ -160,8 +157,8 @@
|
|
|
160
157
|
field={childField}
|
|
161
158
|
value={childValue}
|
|
162
159
|
{style}
|
|
163
|
-
lastElement={index ===
|
|
164
|
-
level={
|
|
160
|
+
lastElement={index === count - 1}
|
|
161
|
+
level={level + 1}
|
|
165
162
|
{shouldExpandNode}
|
|
166
163
|
{clickToExpandNode}
|
|
167
164
|
{beforeExpandChange}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { JsonRenderProps } from './types.js'
|
|
2
|
+
import type { JsonRenderProps, SnippetOverrides } from './types.js'
|
|
3
3
|
import {
|
|
4
4
|
isBigInt,
|
|
5
5
|
isBoolean,
|
|
@@ -15,92 +15,99 @@
|
|
|
15
15
|
const { field, value, style, lastElement, level, snippets }: JsonRenderProps<Primitive> =
|
|
16
16
|
$props()
|
|
17
17
|
|
|
18
|
-
const hasField = $derived(field !== undefined)
|
|
19
18
|
const labelText = $derived(quoteString(field ?? '', style.quotesForFieldNames))
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
| '
|
|
26
|
-
| '
|
|
27
|
-
| '
|
|
28
|
-
| '
|
|
29
|
-
| '
|
|
30
|
-
| '
|
|
20
|
+
// `text`/`valueStyle` are common to every kind; only the discriminant and
|
|
21
|
+
// its snippet type vary. The intersection keeps `primitive.kind === 'x'`
|
|
22
|
+
// narrowing `primitive.snippet` to the one correct Snippet in the template.
|
|
23
|
+
type PrimitiveRender = { text: string; valueStyle: string } & (
|
|
24
|
+
| { kind: 'null'; snippet: SnippetOverrides['null'] }
|
|
25
|
+
| { kind: 'undefined'; snippet: SnippetOverrides['undefined'] }
|
|
26
|
+
| { kind: 'string'; snippet: SnippetOverrides['string'] }
|
|
27
|
+
| { kind: 'boolean'; snippet: SnippetOverrides['boolean'] }
|
|
28
|
+
| { kind: 'number'; snippet: SnippetOverrides['number'] }
|
|
29
|
+
| { kind: 'bigint'; snippet: SnippetOverrides['bigint'] }
|
|
30
|
+
| { kind: 'date'; snippet: SnippetOverrides['date'] }
|
|
31
|
+
| { kind: 'function'; snippet: SnippetOverrides['function'] }
|
|
32
|
+
| { kind: 'other'; snippet: undefined }
|
|
33
|
+
)
|
|
31
34
|
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (isDate(value)) return 'date'
|
|
43
|
-
if (isFunction(value)) return 'function'
|
|
44
|
-
return 'other'
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
type Rendered = { text: string; valueStyle: string }
|
|
48
|
-
const rendered = $derived.by<Rendered>(() => {
|
|
49
|
-
switch (kind) {
|
|
50
|
-
case 'null':
|
|
51
|
-
return { text: 'null', valueStyle: style.nullValue }
|
|
52
|
-
case 'undefined':
|
|
53
|
-
return { text: 'undefined', valueStyle: style.undefinedValue }
|
|
54
|
-
case 'string':
|
|
55
|
-
return {
|
|
56
|
-
text: quoteStringValue(
|
|
57
|
-
value as string,
|
|
58
|
-
!style.noQuotesForStringValues,
|
|
59
|
-
style.stringifyStringValues
|
|
60
|
-
),
|
|
61
|
-
valueStyle: style.stringValue
|
|
62
|
-
}
|
|
63
|
-
case 'boolean':
|
|
64
|
-
return {
|
|
65
|
-
text: (value as boolean) ? 'true' : 'false',
|
|
66
|
-
valueStyle: style.booleanValue
|
|
67
|
-
}
|
|
68
|
-
case 'number':
|
|
69
|
-
return { text: String(value), valueStyle: style.numberValue }
|
|
70
|
-
case 'bigint':
|
|
71
|
-
return {
|
|
72
|
-
text: `${(value as bigint).toString()}n`,
|
|
73
|
-
valueStyle: style.numberValue
|
|
74
|
-
}
|
|
75
|
-
case 'date':
|
|
76
|
-
return { text: (value as Date).toISOString(), valueStyle: style.otherValue }
|
|
77
|
-
case 'function':
|
|
78
|
-
return { text: 'function() { }', valueStyle: style.otherValue }
|
|
79
|
-
default:
|
|
80
|
-
return { text: String(value), valueStyle: style.otherValue }
|
|
35
|
+
// One predicate walk returns everything the row needs, avoiding three
|
|
36
|
+
// separate reactions for the same primitive classification.
|
|
37
|
+
const primitive = $derived.by<PrimitiveRender>(() => {
|
|
38
|
+
if (value === null) {
|
|
39
|
+
return {
|
|
40
|
+
kind: 'null',
|
|
41
|
+
text: 'null',
|
|
42
|
+
valueStyle: style.nullValue,
|
|
43
|
+
snippet: snippets.null
|
|
44
|
+
}
|
|
81
45
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
return {
|
|
48
|
+
kind: 'undefined',
|
|
49
|
+
text: 'undefined',
|
|
50
|
+
valueStyle: style.undefinedValue,
|
|
51
|
+
snippet: snippets.undefined
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (isString(value)) {
|
|
55
|
+
return {
|
|
56
|
+
kind: 'string',
|
|
57
|
+
text: quoteStringValue(
|
|
58
|
+
value,
|
|
59
|
+
!style.noQuotesForStringValues,
|
|
60
|
+
style.stringifyStringValues
|
|
61
|
+
),
|
|
62
|
+
valueStyle: style.stringValue,
|
|
63
|
+
snippet: snippets.string
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (isBoolean(value)) {
|
|
67
|
+
return {
|
|
68
|
+
kind: 'boolean',
|
|
69
|
+
text: value ? 'true' : 'false',
|
|
70
|
+
valueStyle: style.booleanValue,
|
|
71
|
+
snippet: snippets.boolean
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (isNumber(value)) {
|
|
75
|
+
return {
|
|
76
|
+
kind: 'number',
|
|
77
|
+
text: String(value),
|
|
78
|
+
valueStyle: style.numberValue,
|
|
79
|
+
snippet: snippets.number
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (isBigInt(value)) {
|
|
83
|
+
return {
|
|
84
|
+
kind: 'bigint',
|
|
85
|
+
text: `${value.toString()}n`,
|
|
86
|
+
valueStyle: style.numberValue,
|
|
87
|
+
snippet: snippets.bigint
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (isDate(value)) {
|
|
91
|
+
return {
|
|
92
|
+
kind: 'date',
|
|
93
|
+
text: value.toISOString(),
|
|
94
|
+
valueStyle: style.otherValue,
|
|
95
|
+
snippet: snippets.date
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (isFunction(value)) {
|
|
99
|
+
return {
|
|
100
|
+
kind: 'function',
|
|
101
|
+
text: 'function() { }',
|
|
102
|
+
valueStyle: style.otherValue,
|
|
103
|
+
snippet: snippets.function
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
kind: 'other',
|
|
108
|
+
text: String(value),
|
|
109
|
+
valueStyle: style.otherValue,
|
|
110
|
+
snippet: undefined
|
|
104
111
|
}
|
|
105
112
|
})
|
|
106
113
|
</script>
|
|
@@ -110,43 +117,43 @@
|
|
|
110
117
|
preserves template whitespace between adjacent elements as visible spaces
|
|
111
118
|
under the container's `white-space: pre-wrap`. React/JSX strips that
|
|
112
119
|
whitespace natively; we hand-collapse it. Per-type snippet branches key
|
|
113
|
-
off
|
|
120
|
+
off the merged primitive render model rather than re-running predicates.
|
|
114
121
|
-->
|
|
115
122
|
<!-- Upstream parity: unselected treeitems omit aria-selected entirely. -->
|
|
116
123
|
<!-- svelte-ignore a11y_role_has_required_aria_props -->
|
|
117
124
|
<div class={style.basicChildStyle} role="treeitem">
|
|
118
125
|
<!-- prettier-ignore -->
|
|
119
|
-
{#if
|
|
126
|
+
{#if field !== undefined}{#if snippets.label}{@render snippets.label({ field: field ?? '', level })}{:else}<span class={style.label}>{labelText}:</span>{/if}{/if}{#if primitive.snippet && primitive.kind === 'null'}{@render primitive.snippet(
|
|
120
127
|
{ value: null, field, level }
|
|
121
|
-
)}{:else if
|
|
128
|
+
)}{:else if primitive.snippet && primitive.kind === 'undefined'}{@render primitive.snippet({
|
|
122
129
|
value: undefined,
|
|
123
130
|
field,
|
|
124
131
|
level
|
|
125
|
-
})}{:else if
|
|
132
|
+
})}{:else if primitive.snippet && primitive.kind === 'string'}{@render primitive.snippet({
|
|
126
133
|
value: value as string,
|
|
127
134
|
field,
|
|
128
135
|
level
|
|
129
|
-
})}{:else if
|
|
136
|
+
})}{:else if primitive.snippet && primitive.kind === 'boolean'}{@render primitive.snippet({
|
|
130
137
|
value: value as boolean,
|
|
131
138
|
field,
|
|
132
139
|
level
|
|
133
|
-
})}{:else if
|
|
140
|
+
})}{:else if primitive.snippet && primitive.kind === 'number'}{@render primitive.snippet({
|
|
134
141
|
value: value as number,
|
|
135
142
|
field,
|
|
136
143
|
level
|
|
137
|
-
})}{:else if
|
|
144
|
+
})}{:else if primitive.snippet && primitive.kind === 'bigint'}{@render primitive.snippet({
|
|
138
145
|
value: value as bigint,
|
|
139
146
|
field,
|
|
140
147
|
level
|
|
141
|
-
})}{:else if
|
|
148
|
+
})}{:else if primitive.snippet && primitive.kind === 'date'}{@render primitive.snippet({
|
|
142
149
|
value: value as Date,
|
|
143
150
|
field,
|
|
144
151
|
level
|
|
145
|
-
})}{:else if
|
|
152
|
+
})}{:else if primitive.snippet && primitive.kind === 'function'}{@render primitive.snippet({
|
|
146
153
|
// trunk-ignore(eslint/@typescript-eslint/no-unsafe-function-type)
|
|
147
154
|
value: value as Function,
|
|
148
155
|
field,
|
|
149
156
|
level
|
|
150
|
-
})}{:else}<span class={
|
|
157
|
+
})}{:else}<span class={primitive.valueStyle}>{primitive.text}</span
|
|
151
158
|
>{/if}{#if !lastElement}<span class={style.punctuation}>,</span>{/if}
|
|
152
159
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-json-view-lite",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Fast, tiny JSON tree viewer for Svelte 5 — port of react-json-view-lite with runes, SSR, snippet overrides, and zero runtime dependencies",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|