@humanspeak/svelte-json-view-lite 0.1.0

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 ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Humanspeak, Inc.
4
+
5
+ Copyright (c) 2024 AnyRoad (original react-json-view-lite, https://github.com/AnyRoad/react-json-view-lite)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # @humanspeak/svelte-json-view-lite
2
+
3
+ Fast, tiny JSON tree viewer for Svelte 5 — a port of
4
+ [react-json-view-lite](https://github.com/AnyRoad/react-json-view-lite)
5
+ (MIT © 2024 AnyRoad) with runes, SSR, per-type snippet overrides, and zero
6
+ runtime dependencies.
7
+
8
+ ## Features
9
+
10
+ - Svelte 5 runes throughout (`$props`, `$state`, `$derived`, `$effect`).
11
+ - Drop-in API parity with `react-json-view-lite`: same prop names, themes,
12
+ and strategy helpers.
13
+ - Per-type `Snippet` overrides for custom value rendering (strings, numbers,
14
+ dates, etc.) — new in the Svelte port.
15
+ - SSR-safe: uses `$props.id()` for stable `aria-controls` linkage across
16
+ server/client.
17
+ - Keyboard accessible: roving tabindex + `ArrowUp`/`ArrowDown`/`ArrowLeft`/
18
+ `ArrowRight` navigation, full WAI-ARIA treeview semantics.
19
+ - Ships built-in light and dark themes via CSS Modules.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm i -S @humanspeak/svelte-json-view-lite
25
+ # or
26
+ pnpm add @humanspeak/svelte-json-view-lite
27
+ ```
28
+
29
+ ## Basic usage
30
+
31
+ ```svelte
32
+ <script lang="ts">
33
+ import { JsonView, defaultStyles } from '@humanspeak/svelte-json-view-lite'
34
+
35
+ const data = {
36
+ name: 'Ada Lovelace',
37
+ tags: ['admin', 'beta'],
38
+ active: true,
39
+ joined: new Date('2024-01-15')
40
+ }
41
+ </script>
42
+
43
+ <JsonView {data} style={defaultStyles} />
44
+ ```
45
+
46
+ ## Props
47
+
48
+ | Prop | Type | Default | Description |
49
+ | ----------------------- | ---------------------------------------- | --------------- | ---------------------------------------------------------------------------------- |
50
+ | `data` | `object \| unknown[]` | — | The JSON-shaped value to render. |
51
+ | `style` | `Partial<StyleProps>` | `defaultStyles` | Classname-map that themes every slot. |
52
+ | `shouldExpandNode` | `(level, value, field?) => boolean` | `allExpanded` | Initial-expand strategy per node. |
53
+ | `clickToExpandNode` | `boolean` | `false` | When true, clicking the field label also toggles the node. |
54
+ | `beforeExpandChange` | `(event: NodeExpandingEvent) => boolean` | — | Return `false` to veto an expand/collapse transition. |
55
+ | `compactTopLevel` | `boolean` | `false` | Spread root-object entries instead of nesting them under a single root expander. |
56
+ | `string`, `number`, ... | `Snippet<[{ value, field?, level }]>` | — | Optional per-type renderer overrides. See [Snippet overrides](#snippet-overrides). |
57
+
58
+ Any additional HTML attributes (`aria-*`, `data-*`, `id`, `class`, etc.) are
59
+ forwarded onto the root `<div role="tree">`.
60
+
61
+ ## Themes
62
+
63
+ Two themes ship out of the box:
64
+
65
+ ```svelte
66
+ <script lang="ts">
67
+ import { JsonView, defaultStyles, darkStyles } from '@humanspeak/svelte-json-view-lite'
68
+ </script>
69
+
70
+ <JsonView data={json} style={darkStyles} />
71
+ ```
72
+
73
+ Override individual slots by spreading:
74
+
75
+ ```svelte
76
+ <JsonView
77
+ {data}
78
+ style={{
79
+ ...defaultStyles,
80
+ stringValue: 'my-custom-string-class',
81
+ punctuation: 'my-custom-punctuation-class'
82
+ }}
83
+ />
84
+ ```
85
+
86
+ ## Snippet overrides
87
+
88
+ Unlike the React lib, `svelte-json-view-lite` exposes typed `Snippet`
89
+ overrides for every primitive type plus the field label. Each snippet
90
+ receives `{ value, field?, level }`; omit the snippet to fall through to
91
+ the default rendering.
92
+
93
+ ```svelte
94
+ <script lang="ts">
95
+ import {
96
+ JsonView,
97
+ type DateSnippetProps,
98
+ type StringSnippetProps
99
+ } from '@humanspeak/svelte-json-view-lite'
100
+
101
+ const data = {
102
+ joined: new Date('2024-01-15'),
103
+ docs: 'https://example.com/docs'
104
+ }
105
+
106
+ function relative(d: Date): string {
107
+ const days = Math.round((d.getTime() - Date.now()) / 86_400_000)
108
+ return new Intl.RelativeTimeFormat('en').format(days, 'day')
109
+ }
110
+ </script>
111
+
112
+ <JsonView {data}>
113
+ {#snippet date({ value }: DateSnippetProps)}
114
+ <span title={value.toISOString()}>{relative(value)}</span>
115
+ {/snippet}
116
+ {#snippet string({ value }: StringSnippetProps)}
117
+ {#if /^https?:/.test(value)}
118
+ <a href={value}>{value}</a>
119
+ {:else}
120
+ "{value}"
121
+ {/if}
122
+ {/snippet}
123
+ </JsonView>
124
+ ```
125
+
126
+ Available snippets: `string`, `number`, `boolean`, `null`, `undefined`,
127
+ `bigint`, `date`, `function`, `label`. Their typed prop interfaces
128
+ (`StringSnippetProps`, `LabelSnippetProps`, etc.) are all exported from
129
+ the package root.
130
+
131
+ ## Expand strategies
132
+
133
+ ```svelte
134
+ <script lang="ts">
135
+ import { JsonView, collapseAllNested, allExpanded } from '@humanspeak/svelte-json-view-lite'
136
+ </script>
137
+
138
+ <!-- expand only the root, collapse every child -->
139
+ <JsonView {data} shouldExpandNode={collapseAllNested} />
140
+
141
+ <!-- expand everything (default) -->
142
+ <JsonView {data} shouldExpandNode={allExpanded} />
143
+
144
+ <!-- custom: expand up to depth 2 -->
145
+ <JsonView {data} shouldExpandNode={(level) => level < 2} />
146
+ ```
147
+
148
+ ## Migrating from `react-json-view-lite`
149
+
150
+ The Svelte API preserves the React prop names and theme-object shape. Only
151
+ two differences exist:
152
+
153
+ | React | Svelte |
154
+ | --------------------------- | ---------------------------------------------------------- |
155
+ | `style.ariaLables` (typoed) | `style.ariaLabels` (fixed; typo still honored with a warn) |
156
+ | — (not supported) | Per-type `Snippet` overrides |
157
+
158
+ Everything else — `style`, `clickToExpandNode`, `compactTopLevel`,
159
+ `beforeExpandChange`, `shouldExpandNode`, `defaultStyles`, `darkStyles`,
160
+ `allExpanded`, `collapseAllNested` — works identically.
161
+
162
+ ## Accessibility
163
+
164
+ - Root element has `role="tree"` with `aria-label="JSON view"` (overridable).
165
+ - Every expandable node is a `role="treeitem"` with live `aria-expanded`
166
+ and, when open, `aria-controls` pointing at its child `<ul role="group">`.
167
+ - Keyboard support follows the [WAI-ARIA 1.2 Treeview pattern]:
168
+ - `ArrowRight` expands, `ArrowLeft` collapses.
169
+ - `ArrowDown` / `ArrowUp` move focus between expanders (wrapping).
170
+ - Roving `tabindex` keeps only one expander in the tab order.
171
+
172
+ [WAI-ARIA 1.2 Treeview pattern]: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
173
+
174
+ ## Development
175
+
176
+ ```bash
177
+ pnpm install
178
+ pnpm dev # launch SvelteKit playground on :8233
179
+ pnpm check # svelte-check
180
+ pnpm test # vitest + coverage
181
+ pnpm build # vite build + svelte-package + publint
182
+ ```
183
+
184
+ ## License
185
+
186
+ MIT. Copyright © 2024-2026 Humanspeak, Inc. Original
187
+ `react-json-view-lite` © 2024 AnyRoad.
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import ExpandableObject from './ExpandableObject.svelte'
3
+ import JsonPrimitiveValue from './JsonPrimitiveValue.svelte'
4
+ import type { JsonRenderProps } from './types.js'
5
+ import { isArray, isDate, isFunction, isObject } from './utils/dataTypeDetection.js'
6
+
7
+ // Declared as constants because Svelte markup parses `"{"` as an
8
+ // expression opener, and eslint's no-useless-mustaches objects to
9
+ // `{'{'}` inline.
10
+ const OBJECT_OPEN = '{'
11
+ const OBJECT_CLOSE = '}'
12
+
13
+ const props: JsonRenderProps<unknown> = $props()
14
+ const value = $derived(props.value)
15
+
16
+ // Array check first — arrays are also objects, so order matters.
17
+ const isArr = $derived(isArray(value))
18
+ const isObj = $derived(isObject(value) && !isDate(value) && !isFunction(value))
19
+ </script>
20
+
21
+ {#if isArr}
22
+ <ExpandableObject
23
+ {...props}
24
+ value={value as unknown[]}
25
+ data={(value as unknown[]).map((el) => [undefined, el])}
26
+ openBracket="["
27
+ closeBracket="]"
28
+ />
29
+ {:else if isObj}
30
+ <ExpandableObject
31
+ {...props}
32
+ value={value as object}
33
+ data={Object.keys(value as object).map((k) => [k, (value as Record<string, unknown>)[k]])}
34
+ openBracket={OBJECT_OPEN}
35
+ closeBracket={OBJECT_CLOSE}
36
+ />
37
+ {:else}
38
+ <JsonPrimitiveValue
39
+ {...props}
40
+ value={value as
41
+ | string
42
+ | number
43
+ | boolean
44
+ | bigint
45
+ | Date
46
+ // trunk-ignore(eslint/@typescript-eslint/no-unsafe-function-type)
47
+ | Function
48
+ | null
49
+ | undefined}
50
+ />
51
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { JsonRenderProps } from './types.js';
2
+ declare const DataRender: import("svelte").Component<JsonRenderProps<unknown>, {}, "">;
3
+ type DataRender = ReturnType<typeof DataRender>;
4
+ export default DataRender;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { EmptyRenderProps } from './types.js'
3
+ import { quoteString } from './utils/quoteString.js'
4
+
5
+ const { field, openBracket, closeBracket, lastElement, style }: EmptyRenderProps = $props()
6
+ const hasField = $derived(field !== undefined)
7
+ const labelText = $derived(quoteString(field ?? '', style.quotesForFieldNames))
8
+ </script>
9
+
10
+ <!--
11
+ Label + brackets collapsed onto one prettier-ignored line because Svelte
12
+ preserves template whitespace between adjacent elements as visible spaces
13
+ under the container's `white-space: pre-wrap`. React/JSX strips that
14
+ whitespace natively; we hand-collapse it. Brackets and trailing comma also
15
+ live in a single punctuation span so the `.punctuation-base + .punctuation-base`
16
+ adjacent-sibling margin rule isn't needed here.
17
+ -->
18
+ <div class={style.basicChildStyle} role="treeitem" aria-selected={false}>
19
+ <!-- prettier-ignore -->
20
+ {#if hasField}<span class={style.label}>{labelText}:</span>{/if}<span class={style.punctuation}
21
+ >{openBracket}{closeBracket}{lastElement ? '' : ','}</span
22
+ >
23
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { EmptyRenderProps } from './types.js';
2
+ declare const EmptyObject: import("svelte").Component<EmptyRenderProps, {}, "">;
3
+ type EmptyObject = ReturnType<typeof EmptyObject>;
4
+ export default EmptyObject;
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import DataRender from './DataRender.svelte'
3
+ import EmptyObject from './EmptyObject.svelte'
4
+ import type { AriaLabels, ExpandableRenderProps } from './types.js'
5
+ import { quoteString } from './utils/quoteString.js'
6
+
7
+ const {
8
+ field,
9
+ value,
10
+ data,
11
+ lastElement,
12
+ openBracket,
13
+ closeBracket,
14
+ level,
15
+ style,
16
+ shouldExpandNode,
17
+ clickToExpandNode,
18
+ outerRef,
19
+ beforeExpandChange,
20
+ snippets
21
+ }: ExpandableRenderProps = $props()
22
+
23
+ // Lazy init — runs once on mount (react useState(() => ...) equivalent).
24
+ // svelte-ignore state_referenced_locally
25
+ let expanded = $state(shouldExpandNode(level, value, field))
26
+
27
+ // React's useRef<boolean>: plain mutable local.
28
+ let shouldExpandNodeCalled = false
29
+
30
+ // Match React useEffect(fn, [shouldExpandNode]) — fire only when the
31
+ // callback identity changes. We intentionally avoid reading level/value/
32
+ // field so an ancestor re-render doesn't trigger a respectful collapse.
33
+ $effect(() => {
34
+ const fn = shouldExpandNode
35
+ if (!shouldExpandNodeCalled) {
36
+ shouldExpandNodeCalled = true
37
+ return
38
+ }
39
+ expanded = fn(level, value, field)
40
+ })
41
+
42
+ // SSR-stable unique id for aria-controls linkage (React.useId equivalent).
43
+ const contentsId = $props.id()
44
+
45
+ // bind:this target for focus management.
46
+ let expanderButton = $state<HTMLSpanElement | null>(null)
47
+
48
+ const activeAriaLabels = $derived<AriaLabels>(
49
+ style.ariaLabels ??
50
+ style.ariaLables ?? {
51
+ collapseJson: 'collapse JSON',
52
+ expandJson: 'expand JSON'
53
+ }
54
+ )
55
+ const expanderIconStyle = $derived(expanded ? style.collapseIcon : style.expandIcon)
56
+ const ariaLabel = $derived(
57
+ expanded ? activeAriaLabels.collapseJson : activeAriaLabels.expandJson
58
+ )
59
+ const childLevel = $derived(level + 1)
60
+ const lastIndex = $derived(data.length - 1)
61
+ const hasField = $derived(field !== undefined)
62
+ const labelText = $derived(quoteString(field ?? '', style.quotesForFieldNames))
63
+
64
+ function setExpandWithCallback(newExpandValue: boolean) {
65
+ if (expanded === newExpandValue) return
66
+ if (beforeExpandChange && !beforeExpandChange({ level, value, field, newExpandValue })) {
67
+ return
68
+ }
69
+ expanded = newExpandValue
70
+ }
71
+
72
+ function onKeyDown(e: KeyboardEvent) {
73
+ if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
74
+ e.preventDefault()
75
+ setExpandWithCallback(e.key === 'ArrowRight')
76
+ return
77
+ }
78
+ if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return
79
+ e.preventDefault()
80
+ const direction = e.key === 'ArrowUp' ? -1 : 1
81
+ const outer = outerRef.current
82
+ if (!outer) return
83
+ const buttons = outer.querySelectorAll<HTMLElement>('[role=button]')
84
+ let currentIndex = -1
85
+ for (let i = 0; i < buttons.length; i++) {
86
+ if (buttons[i].tabIndex === 0) {
87
+ currentIndex = i
88
+ break
89
+ }
90
+ }
91
+ if (currentIndex < 0) return
92
+ const nextIndex = (currentIndex + direction + buttons.length) % buttons.length
93
+ buttons[currentIndex].tabIndex = -1
94
+ buttons[nextIndex].tabIndex = 0
95
+ buttons[nextIndex].focus()
96
+ }
97
+
98
+ function onClick() {
99
+ setExpandWithCallback(!expanded)
100
+ if (!expanderButton) return
101
+ const prev = outerRef.current?.querySelector<HTMLElement>('[role=button][tabindex="0"]')
102
+ if (prev) prev.tabIndex = -1
103
+ expanderButton.tabIndex = 0
104
+ expanderButton.focus()
105
+ }
106
+ </script>
107
+
108
+ {#if data.length === 0}
109
+ <EmptyObject {field} {openBracket} {closeBracket} {lastElement} {style} />
110
+ {:else}
111
+ <div
112
+ class={style.basicChildStyle}
113
+ role="treeitem"
114
+ aria-expanded={expanded}
115
+ aria-selected={false}
116
+ >
117
+ <!--
118
+ The entire inline sequence inside a row lives on a single
119
+ prettier-ignored line because Svelte preserves template whitespace
120
+ between adjacent elements (expander→label→openBracket→children→
121
+ closeBracket) as visible spaces under the container's
122
+ `white-space: pre-wrap`. React/JSX strips that whitespace natively;
123
+ we have to hand-collapse it. The `<ul>` of children is block-level
124
+ so the whitespace around it is not layout-significant, but we keep
125
+ it tight anyway for a consistent rule.
126
+ -->
127
+ <!-- prettier-ignore -->
128
+ <span bind:this={expanderButton} 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 hasField}{#if snippets.label}{@render snippets.label(
129
+ { field: field ?? '', level }
130
+ )}{:else if clickToExpandNode}<!-- svelte-ignore a11y_no_static_element_interactions --><span
131
+ class={style.clickableLabel}
132
+ onclick={onClick}
133
+ onkeydown={onKeyDown}>{labelText}:</span
134
+ >{:else}<span class={style.label}>{labelText}:</span>{/if}{/if}<span
135
+ class={style.punctuation}>{openBracket}</span
136
+ >{#if expanded}<ul id={contentsId} role="group" class={style.childFieldsContainer}>
137
+ {#each data as [childField, childValue], index (childField ?? index)}<DataRender
138
+ field={childField}
139
+ value={childValue}
140
+ {style}
141
+ lastElement={index === lastIndex}
142
+ level={childLevel}
143
+ {shouldExpandNode}
144
+ {clickToExpandNode}
145
+ {beforeExpandChange}
146
+ {outerRef}
147
+ {snippets}
148
+ />{/each}
149
+ </ul>{:else}<!-- svelte-ignore a11y_no_static_element_interactions --><span
150
+ class={style.collapsedContent}
151
+ onclick={onClick}
152
+ onkeydown={onKeyDown}
153
+ ></span>{/if}<span class={style.punctuation}
154
+ >{closeBracket}{lastElement ? '' : ','}</span
155
+ >
156
+ </div>
157
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { ExpandableRenderProps } from './types.js';
2
+ declare const ExpandableObject: import("svelte").Component<ExpandableRenderProps, {}, "">;
3
+ type ExpandableObject = ReturnType<typeof ExpandableObject>;
4
+ export default ExpandableObject;
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import type { JsonRenderProps } from './types.js'
3
+ import {
4
+ isBigInt,
5
+ isBoolean,
6
+ isDate,
7
+ isFunction,
8
+ isNumber,
9
+ isString
10
+ } from './utils/dataTypeDetection.js'
11
+ import { quoteString, quoteStringValue } from './utils/quoteString.js'
12
+
13
+ // trunk-ignore(eslint/@typescript-eslint/no-unsafe-function-type)
14
+ type Primitive = string | number | boolean | bigint | Date | Function | null | undefined
15
+ const { field, value, style, lastElement, level, snippets }: JsonRenderProps<Primitive> =
16
+ $props()
17
+
18
+ const hasField = $derived(field !== undefined)
19
+ const labelText = $derived(quoteString(field ?? '', style.quotesForFieldNames))
20
+
21
+ type Rendered = { text: string; valueStyle: string }
22
+ const rendered = $derived.by<Rendered>(() => {
23
+ if (value === null) return { text: 'null', valueStyle: style.nullValue }
24
+ if (value === undefined) return { text: 'undefined', valueStyle: style.undefinedValue }
25
+ if (isString(value))
26
+ return {
27
+ text: quoteStringValue(
28
+ value,
29
+ !style.noQuotesForStringValues,
30
+ style.stringifyStringValues
31
+ ),
32
+ valueStyle: style.stringValue
33
+ }
34
+ if (isBoolean(value))
35
+ return { text: value ? 'true' : 'false', valueStyle: style.booleanValue }
36
+ if (isNumber(value)) return { text: value.toString(), valueStyle: style.numberValue }
37
+ if (isBigInt(value)) return { text: `${value.toString()}n`, valueStyle: style.numberValue }
38
+ if (isDate(value)) return { text: value.toISOString(), valueStyle: style.otherValue }
39
+ if (isFunction(value)) return { text: 'function() { }', valueStyle: style.otherValue }
40
+ return { text: String(value), valueStyle: style.otherValue }
41
+ })
42
+
43
+ const snippetForValue = $derived.by(() => {
44
+ if (value === null) return snippets.null
45
+ if (value === undefined) return snippets.undefined
46
+ if (isString(value)) return snippets.string
47
+ if (isBoolean(value)) return snippets.boolean
48
+ if (isNumber(value)) return snippets.number
49
+ if (isBigInt(value)) return snippets.bigint
50
+ if (isDate(value)) return snippets.date
51
+ if (isFunction(value)) return snippets.function
52
+ return undefined
53
+ })
54
+ </script>
55
+
56
+ <!--
57
+ The entire row body lives on a single prettier-ignored line because Svelte
58
+ preserves template whitespace between adjacent elements (e.g. between the
59
+ label span and the value span, or between the value and the trailing
60
+ comma). Those whitespace text nodes render as visible spaces under the
61
+ container's `white-space: pre-wrap`. React/JSX strips that whitespace
62
+ natively; we have to hand-collapse it. Every margin we need is provided
63
+ by the CSS module (e.g. `.label { margin-right: 5px; }`).
64
+ -->
65
+ <div class={style.basicChildStyle} role="treeitem" aria-selected={false}>
66
+ <!-- prettier-ignore -->
67
+ {#if hasField}{#if snippets.label}{@render snippets.label({ field: field ?? '', level })}{:else}<span class={style.label}>{labelText}:</span>{/if}{/if}{#if snippetForValue && value === null}{@render snippets.null?.(
68
+ { value: null, field, level }
69
+ )}{:else if snippetForValue && value === undefined}{@render snippets.undefined?.({
70
+ value: undefined,
71
+ field,
72
+ level
73
+ })}{:else if snippetForValue && isString(value)}{@render snippets.string?.({
74
+ value,
75
+ field,
76
+ level
77
+ })}{:else if snippetForValue && isBoolean(value)}{@render snippets.boolean?.({
78
+ value,
79
+ field,
80
+ level
81
+ })}{:else if snippetForValue && isNumber(value)}{@render snippets.number?.({
82
+ value,
83
+ field,
84
+ level
85
+ })}{:else if snippetForValue && isBigInt(value)}{@render snippets.bigint?.({
86
+ value,
87
+ field,
88
+ level
89
+ })}{:else if snippetForValue && isDate(value)}{@render snippets.date?.({
90
+ value,
91
+ field,
92
+ level
93
+ })}{:else if snippetForValue && isFunction(value)}{@render snippets.function?.({
94
+ value,
95
+ field,
96
+ level
97
+ })}{:else}<span class={rendered.valueStyle}>{rendered.text}</span
98
+ >{/if}{#if !lastElement}<span class={style.punctuation}>,</span>{/if}
99
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { JsonRenderProps } from './types.js';
2
+ declare const JsonPrimitiveValue: import("svelte").Component<JsonRenderProps<string | number | bigint | boolean | Function | Date | null | undefined>, {}, "">;
3
+ type JsonPrimitiveValue = ReturnType<typeof JsonPrimitiveValue>;
4
+ export default JsonPrimitiveValue;
@@ -0,0 +1,106 @@
1
+ <script lang="ts">
2
+ import DataRender from './DataRender.svelte'
3
+ import { defaultStyles } from './index.js'
4
+ import type { OuterRef, Props, StyleProps } from './types.js'
5
+ import { isObject } from './utils/dataTypeDetection.js'
6
+ import { allExpanded } from './utils/expandStrategies.js'
7
+
8
+ const {
9
+ data,
10
+ style = {},
11
+ shouldExpandNode = allExpanded,
12
+ clickToExpandNode = false,
13
+ beforeExpandChange,
14
+ compactTopLevel = false,
15
+ string,
16
+ number,
17
+ boolean,
18
+ null: nullSnippet,
19
+ undefined: undefinedSnippet,
20
+ bigint,
21
+ date,
22
+ function: functionSnippet,
23
+ label,
24
+ 'aria-label': ariaLabel = 'JSON view',
25
+ ...rest
26
+ }: Props = $props()
27
+
28
+ let outerElement = $state<HTMLDivElement | null>(null)
29
+
30
+ // Merge user theme onto defaults. Also emit a deprecation warning when the
31
+ // legacy `ariaLables` key (typo in react-json-view-lite) is supplied
32
+ // without the corrected `ariaLabels`; the typoed value is still honored
33
+ // during 1.x so React migrators have a soft landing.
34
+ const mergedStyle = $derived.by<StyleProps>(() => {
35
+ const merged: StyleProps = { ...defaultStyles, ...style }
36
+ // Accept the misspelled ariaLables (from react-json-view-lite) only
37
+ // when the user has not provided an ariaLabels value — users who
38
+ // supply both should have the corrected key win.
39
+ if (style?.ariaLables && !style.ariaLabels) {
40
+ if (typeof console !== 'undefined') {
41
+ console.warn(
42
+ '[svelte-json-view-lite] StyleProps.ariaLables is deprecated (typo preserved from react-json-view-lite); use ariaLabels.'
43
+ )
44
+ }
45
+ merged.ariaLabels = style.ariaLables
46
+ }
47
+ return merged
48
+ })
49
+
50
+ // Getter wrapper so children always see the current bind:this target
51
+ // rather than a frozen snapshot from the first render.
52
+ const outerRef: OuterRef = {
53
+ get current() {
54
+ return outerElement
55
+ }
56
+ }
57
+
58
+ const snippets = $derived({
59
+ string,
60
+ number,
61
+ boolean,
62
+ null: nullSnippet,
63
+ undefined: undefinedSnippet,
64
+ bigint,
65
+ date,
66
+ function: functionSnippet,
67
+ label
68
+ })
69
+ </script>
70
+
71
+ <div
72
+ bind:this={outerElement}
73
+ role="tree"
74
+ aria-label={ariaLabel}
75
+ {...rest}
76
+ class={mergedStyle.container}
77
+ >
78
+ {#if compactTopLevel && isObject(data)}
79
+ {#each Object.entries(data) as [key, value] (key)}
80
+ <DataRender
81
+ field={key}
82
+ {value}
83
+ style={mergedStyle}
84
+ lastElement={true}
85
+ level={1}
86
+ {shouldExpandNode}
87
+ {clickToExpandNode}
88
+ {beforeExpandChange}
89
+ {outerRef}
90
+ {snippets}
91
+ />
92
+ {/each}
93
+ {:else}
94
+ <DataRender
95
+ value={data}
96
+ style={mergedStyle}
97
+ lastElement={true}
98
+ level={0}
99
+ {shouldExpandNode}
100
+ {clickToExpandNode}
101
+ {beforeExpandChange}
102
+ {outerRef}
103
+ {snippets}
104
+ />
105
+ {/if}
106
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { Props } from './types.js';
2
+ declare const JsonView: import("svelte").Component<Props, {}, "">;
3
+ type JsonView = ReturnType<typeof JsonView>;
4
+ export default JsonView;
@@ -0,0 +1,14 @@
1
+ import JsonView from './JsonView.svelte';
2
+ import type { AriaLabels, StyleProps } from './types.js';
3
+ export default JsonView;
4
+ export type { AriaLabels, BigIntSnippetProps, BooleanSnippetProps, DateSnippetProps, FunctionSnippetProps, LabelSnippetProps, NodeExpandingEvent, NullSnippetProps, NumberSnippetProps, Props, SnippetOverrides, StringSnippetProps, StyleProps, UndefinedSnippetProps } from './types.js';
5
+ export { allExpanded, collapseAllNested } from './utils/expandStrategies.js';
6
+ export { JsonView };
7
+ /**
8
+ * Correctly-spelled default aria labels. Introduced in the Svelte port;
9
+ * prefer this over the typoed `StyleProps.ariaLables` shipped by
10
+ * `react-json-view-lite`.
11
+ */
12
+ export declare const defaultAriaLabels: AriaLabels;
13
+ export declare const defaultStyles: StyleProps;
14
+ export declare const darkStyles: StyleProps;
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ import JsonView from './JsonView.svelte';
2
+ import styles from './styles.module.css';
3
+ export default JsonView;
4
+ export { allExpanded, collapseAllNested } from './utils/expandStrategies.js';
5
+ export { JsonView };
6
+ const baseAriaLabels = {
7
+ collapseJson: 'collapse JSON',
8
+ expandJson: 'expand JSON'
9
+ };
10
+ /**
11
+ * Correctly-spelled default aria labels. Introduced in the Svelte port;
12
+ * prefer this over the typoed `StyleProps.ariaLables` shipped by
13
+ * `react-json-view-lite`.
14
+ */
15
+ export const defaultAriaLabels = baseAriaLabels;
16
+ const buildStyles = (variant) => ({
17
+ container: styles[`container-${variant}`],
18
+ basicChildStyle: styles['basic-element-style'],
19
+ childFieldsContainer: styles['child-fields-container'],
20
+ label: styles[`label-${variant}`],
21
+ clickableLabel: styles[`clickable-label-${variant}`],
22
+ nullValue: styles[`value-null-${variant}`],
23
+ undefinedValue: styles[`value-undefined-${variant}`],
24
+ stringValue: styles[`value-string-${variant}`],
25
+ booleanValue: styles[`value-boolean-${variant}`],
26
+ numberValue: styles[`value-number-${variant}`],
27
+ otherValue: styles[`value-other-${variant}`],
28
+ punctuation: styles[`punctuation-${variant}`],
29
+ collapseIcon: styles[`collapse-icon-${variant}`],
30
+ expandIcon: styles[`expand-icon-${variant}`],
31
+ collapsedContent: styles[`collapsed-content-${variant}`],
32
+ noQuotesForStringValues: false,
33
+ quotesForFieldNames: false,
34
+ ariaLabels: baseAriaLabels,
35
+ stringifyStringValues: false
36
+ });
37
+ export const defaultStyles = buildStyles('light');
38
+ export const darkStyles = buildStyles('dark');
@@ -0,0 +1,184 @@
1
+ /* base styles */
2
+
3
+ .container-base {
4
+ line-height: 1.2;
5
+ white-space: pre-wrap;
6
+ white-space: -moz-pre-wrap;
7
+ white-space: -pre-wrap;
8
+ white-space: -o-pre-wrap;
9
+ word-wrap: break-word;
10
+ }
11
+
12
+ .punctuation-base {
13
+ margin-right: 5px;
14
+ font-weight: bold;
15
+ }
16
+
17
+ .punctuation-base + .punctuation-base {
18
+ margin-left: -5px;
19
+ }
20
+
21
+ .pointer {
22
+ cursor: pointer;
23
+ }
24
+
25
+ .expander-base {
26
+ composes: pointer;
27
+ font-size: 1.2em;
28
+ margin-right: 5px;
29
+ user-select: none;
30
+ }
31
+
32
+ .expand-icon::after {
33
+ content: '▸';
34
+ }
35
+
36
+ .collapse-icon::after {
37
+ content: '▾';
38
+ }
39
+
40
+ .collapsed-content-base {
41
+ composes: pointer;
42
+ margin-right: 5px;
43
+ }
44
+
45
+ .collapsed-content-base::after {
46
+ content: '...';
47
+ font-size: 0.8em;
48
+ }
49
+
50
+ .container-light {
51
+ composes: container-base;
52
+ background: #eee;
53
+ }
54
+
55
+ .basic-element-style {
56
+ margin: 0;
57
+ padding: 0 10px;
58
+ }
59
+
60
+ .child-fields-container {
61
+ margin: 0;
62
+ padding: 0;
63
+ }
64
+
65
+ /* default light style */
66
+ .label-light {
67
+ font-weight: 600;
68
+ margin-right: 5px;
69
+ color: #000000;
70
+ }
71
+
72
+ .clickable-label-light {
73
+ composes: label-light;
74
+ composes: pointer;
75
+ }
76
+
77
+ .punctuation-light {
78
+ composes: punctuation-base;
79
+ color: #000000;
80
+ }
81
+
82
+ .value-null-light {
83
+ color: #df113a;
84
+ }
85
+
86
+ .value-undefined-light {
87
+ color: #df113a;
88
+ }
89
+
90
+ .value-string-light {
91
+ color: rgb(42, 63, 60);
92
+ }
93
+
94
+ .value-number-light {
95
+ color: #0b75f5;
96
+ }
97
+
98
+ .value-boolean-light {
99
+ color: rgb(70, 144, 56);
100
+ }
101
+
102
+ .value-other-light {
103
+ color: #43413d;
104
+ }
105
+
106
+ .collapse-icon-light {
107
+ composes: expander-base;
108
+ composes: collapse-icon;
109
+ color: #000000;
110
+ }
111
+
112
+ .expand-icon-light {
113
+ composes: expander-base;
114
+ composes: expand-icon;
115
+ color: #000000;
116
+ }
117
+
118
+ .collapsed-content-light {
119
+ composes: collapsed-content-base;
120
+ color: #000000;
121
+ }
122
+
123
+ /* default dark style */
124
+ .container-dark {
125
+ background: rgb(0, 43, 54);
126
+ composes: container-base;
127
+ }
128
+
129
+ .expand-icon-dark {
130
+ composes: expander-base;
131
+ composes: expand-icon;
132
+ color: rgb(253, 246, 227);
133
+ }
134
+
135
+ .collapse-icon-dark {
136
+ composes: expander-base;
137
+ composes: collapse-icon;
138
+ color: rgb(253, 246, 227);
139
+ }
140
+
141
+ .collapsed-content-dark {
142
+ composes: collapsed-content-base;
143
+ color: rgb(253, 246, 227);
144
+ }
145
+
146
+ .label-dark {
147
+ font-weight: bolder;
148
+ margin-right: 5px;
149
+ color: rgb(253, 246, 227);
150
+ }
151
+
152
+ .clickable-label-dark {
153
+ composes: label-dark;
154
+ composes: pointer;
155
+ }
156
+
157
+ .punctuation-dark {
158
+ composes: punctuation-base;
159
+ color: rgb(253, 246, 227);
160
+ }
161
+
162
+ .value-null-dark {
163
+ color: rgb(129, 181, 172);
164
+ }
165
+
166
+ .value-undefined-dark {
167
+ color: rgb(129, 181, 172);
168
+ }
169
+
170
+ .value-string-dark {
171
+ color: rgb(203, 75, 22);
172
+ }
173
+
174
+ .value-number-dark {
175
+ color: rgb(211, 54, 130);
176
+ }
177
+
178
+ .value-boolean-dark {
179
+ color: rgb(174, 129, 255);
180
+ }
181
+
182
+ .value-other-dark {
183
+ color: rgb(38, 139, 210);
184
+ }
@@ -0,0 +1,145 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ /**
4
+ * Event payload passed to `beforeExpandChange`. Return `false` to veto the
5
+ * expand/collapse transition.
6
+ */
7
+ export interface NodeExpandingEvent {
8
+ level: number;
9
+ value: unknown;
10
+ field?: string;
11
+ newExpandValue: boolean;
12
+ }
13
+ /** Screen-reader labels applied to expander buttons. */
14
+ export interface AriaLabels {
15
+ collapseJson: string;
16
+ expandJson: string;
17
+ }
18
+ /**
19
+ * Classname-map used to theme every slot in the viewer. The `defaultStyles`
20
+ * and `darkStyles` exports provide ready-made instances; consumers can spread
21
+ * and override individual keys to customize the theme.
22
+ */
23
+ export interface StyleProps {
24
+ container: string;
25
+ basicChildStyle: string;
26
+ label: string;
27
+ clickableLabel: string;
28
+ nullValue: string;
29
+ undefinedValue: string;
30
+ numberValue: string;
31
+ stringValue: string;
32
+ booleanValue: string;
33
+ otherValue: string;
34
+ punctuation: string;
35
+ expandIcon: string;
36
+ collapseIcon: string;
37
+ collapsedContent: string;
38
+ childFieldsContainer: string;
39
+ noQuotesForStringValues?: boolean;
40
+ quotesForFieldNames?: boolean;
41
+ /**
42
+ * Correctly-spelled aria labels. New in the Svelte port; `ariaLables`
43
+ * (sic) continues to be accepted at runtime for one minor version with a
44
+ * deprecation warning.
45
+ */
46
+ ariaLabels: AriaLabels;
47
+ /**
48
+ * @deprecated Use `ariaLabels` (correctly spelled). The typoed key is
49
+ * accepted at runtime for parity with `react-json-view-lite`; it will be
50
+ * removed in 2.0.
51
+ */
52
+ ariaLables?: AriaLabels;
53
+ stringifyStringValues: boolean;
54
+ }
55
+ /** Data passed to every per-type snippet override. */
56
+ export interface ValueSnippetProps<T> {
57
+ value: T;
58
+ field?: string;
59
+ level: number;
60
+ }
61
+ export type StringSnippetProps = ValueSnippetProps<string>;
62
+ export type NumberSnippetProps = ValueSnippetProps<number>;
63
+ export type BooleanSnippetProps = ValueSnippetProps<boolean>;
64
+ export type NullSnippetProps = ValueSnippetProps<null>;
65
+ export type UndefinedSnippetProps = ValueSnippetProps<undefined>;
66
+ export type BigIntSnippetProps = ValueSnippetProps<bigint>;
67
+ export type DateSnippetProps = ValueSnippetProps<Date>;
68
+ export type FunctionSnippetProps = ValueSnippetProps<Function>;
69
+ /** Data passed to the `label` snippet override for field names. */
70
+ export interface LabelSnippetProps {
71
+ field: string;
72
+ level: number;
73
+ }
74
+ /**
75
+ * Bag of optional per-type snippets that replace default value rendering.
76
+ * Each snippet receives the typed value, its field name (if any), and the
77
+ * depth of the node.
78
+ */
79
+ export interface SnippetOverrides {
80
+ string?: Snippet<[StringSnippetProps]>;
81
+ number?: Snippet<[NumberSnippetProps]>;
82
+ boolean?: Snippet<[BooleanSnippetProps]>;
83
+ null?: Snippet<[NullSnippetProps]>;
84
+ undefined?: Snippet<[UndefinedSnippetProps]>;
85
+ bigint?: Snippet<[BigIntSnippetProps]>;
86
+ date?: Snippet<[DateSnippetProps]>;
87
+ function?: Snippet<[FunctionSnippetProps]>;
88
+ label?: Snippet<[LabelSnippetProps]>;
89
+ }
90
+ /** Public props accepted by `<JsonView>`. */
91
+ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'data' | 'style'> {
92
+ data: object | unknown[];
93
+ style?: Partial<StyleProps>;
94
+ shouldExpandNode?: (_level: number, _value: unknown, _field?: string) => boolean;
95
+ clickToExpandNode?: boolean;
96
+ beforeExpandChange?: (_event: NodeExpandingEvent) => boolean;
97
+ compactTopLevel?: boolean;
98
+ string?: Snippet<[StringSnippetProps]>;
99
+ number?: Snippet<[NumberSnippetProps]>;
100
+ boolean?: Snippet<[BooleanSnippetProps]>;
101
+ null?: Snippet<[NullSnippetProps]>;
102
+ undefined?: Snippet<[UndefinedSnippetProps]>;
103
+ bigint?: Snippet<[BigIntSnippetProps]>;
104
+ date?: Snippet<[DateSnippetProps]>;
105
+ function?: Snippet<[FunctionSnippetProps]>;
106
+ label?: Snippet<[LabelSnippetProps]>;
107
+ }
108
+ /**
109
+ * Reference wrapper passed from the root down to every expandable node so
110
+ * that cross-sibling keyboard navigation can query `[role=button]` elements
111
+ * scoped to the tree. Using a getter ensures the child always reads the
112
+ * current `bind:this` target rather than a frozen snapshot.
113
+ */
114
+ export interface OuterRef {
115
+ readonly current: HTMLDivElement | null;
116
+ }
117
+ /** Internal shared props threaded through every renderer. Not exported. */
118
+ export interface CommonRenderProps {
119
+ lastElement: boolean;
120
+ level: number;
121
+ style: StyleProps;
122
+ shouldExpandNode: (_level: number, _value: unknown, _field?: string) => boolean;
123
+ clickToExpandNode: boolean;
124
+ outerRef: OuterRef;
125
+ beforeExpandChange?: (_event: NodeExpandingEvent) => boolean;
126
+ snippets: SnippetOverrides;
127
+ }
128
+ export interface JsonRenderProps<T> extends CommonRenderProps {
129
+ field?: string;
130
+ value: T;
131
+ }
132
+ export interface ExpandableRenderProps extends CommonRenderProps {
133
+ field?: string;
134
+ value: object | unknown[];
135
+ data: Array<[string | undefined, unknown]>;
136
+ openBracket: string;
137
+ closeBracket: string;
138
+ }
139
+ export interface EmptyRenderProps {
140
+ field?: string;
141
+ openBracket: string;
142
+ closeBracket: string;
143
+ lastElement: boolean;
144
+ style: StyleProps;
145
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export declare const isBoolean: (data: unknown) => data is boolean;
2
+ export declare const isNumber: (data: unknown) => data is number;
3
+ export declare const isBigInt: (data: unknown) => data is bigint;
4
+ export declare const isDate: (data: unknown) => data is Date;
5
+ export declare const isString: (data: unknown) => data is string;
6
+ export declare const isArray: (data: unknown) => data is unknown[];
7
+ export declare const isObject: (data: unknown) => data is object;
8
+ export declare const isNull: (data: unknown) => data is null;
9
+ export declare const isUndefined: (data: unknown) => data is undefined;
10
+ export declare const isFunction: (data: unknown) => data is Function;
@@ -0,0 +1,31 @@
1
+ export const isBoolean = (data) => {
2
+ return typeof data === 'boolean' || data instanceof Boolean;
3
+ };
4
+ export const isNumber = (data) => {
5
+ return typeof data === 'number' || data instanceof Number;
6
+ };
7
+ export const isBigInt = (data) => {
8
+ return typeof data === 'bigint' || data instanceof BigInt;
9
+ };
10
+ export const isDate = (data) => {
11
+ return !!data && data instanceof Date;
12
+ };
13
+ export const isString = (data) => {
14
+ return typeof data === 'string' || data instanceof String;
15
+ };
16
+ export const isArray = (data) => {
17
+ return Array.isArray(data);
18
+ };
19
+ export const isObject = (data) => {
20
+ return typeof data === 'object' && data !== null;
21
+ };
22
+ export const isNull = (data) => {
23
+ return data === null;
24
+ };
25
+ export const isUndefined = (data) => {
26
+ return data === undefined;
27
+ };
28
+ // trunk-ignore(eslint/@typescript-eslint/no-unsafe-function-type)
29
+ export const isFunction = (data) => {
30
+ return !!data && data instanceof Object && typeof data === 'function';
31
+ };
@@ -0,0 +1,4 @@
1
+ /** `shouldExpandNode` strategy that auto-expands every node. */
2
+ export declare const allExpanded: () => boolean;
3
+ /** `shouldExpandNode` strategy that auto-expands only the root node. */
4
+ export declare const collapseAllNested: (level: number) => boolean;
@@ -0,0 +1,4 @@
1
+ /** `shouldExpandNode` strategy that auto-expands every node. */
2
+ export const allExpanded = () => true;
3
+ /** `shouldExpandNode` strategy that auto-expands only the root node. */
4
+ export const collapseAllNested = (level) => level < 1;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Formats a field label. Empty labels always appear quoted so the colon is
3
+ * still rendered; non-empty labels are quoted only when the theme sets
4
+ * `quotesForFieldNames`. Matches `react-json-view-lite` exactly.
5
+ */
6
+ export declare function quoteString(value: string, quoted?: boolean): string;
7
+ /**
8
+ * Formats a string value for display. When `stringify` is true, `JSON.stringify`
9
+ * is used to escape special characters; otherwise `quoted` simply toggles
10
+ * surrounding quotes. Empty strings intentionally render without quotes to
11
+ * avoid stacking quotes on inline CSS snippets.
12
+ */
13
+ export declare function quoteStringValue(value: string, quoted: boolean, stringify: boolean): string;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Formats a field label. Empty labels always appear quoted so the colon is
3
+ * still rendered; non-empty labels are quoted only when the theme sets
4
+ * `quotesForFieldNames`. Matches `react-json-view-lite` exactly.
5
+ */
6
+ export function quoteString(value, quoted = false) {
7
+ return !value || quoted ? `"${value}"` : value;
8
+ }
9
+ /**
10
+ * Formats a string value for display. When `stringify` is true, `JSON.stringify`
11
+ * is used to escape special characters; otherwise `quoted` simply toggles
12
+ * surrounding quotes. Empty strings intentionally render without quotes to
13
+ * avoid stacking quotes on inline CSS snippets.
14
+ */
15
+ export function quoteStringValue(value, quoted, stringify) {
16
+ if (stringify) {
17
+ return JSON.stringify(value);
18
+ }
19
+ return quoted ? `"${value}"` : value;
20
+ }
package/package.json ADDED
@@ -0,0 +1,127 @@
1
+ {
2
+ "name": "@humanspeak/svelte-json-view-lite",
3
+ "version": "0.1.0",
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
+ "keywords": [
6
+ "svelte",
7
+ "svelte5",
8
+ "sveltekit",
9
+ "json",
10
+ "json-view",
11
+ "json-tree",
12
+ "viewer",
13
+ "tree",
14
+ "typescript",
15
+ "runes",
16
+ "lite",
17
+ "renderer",
18
+ "component"
19
+ ],
20
+ "homepage": "https://github.com/humanspeak/svelte-json-view-lite",
21
+ "bugs": {
22
+ "url": "https://github.com/humanspeak/svelte-json-view-lite/issues"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/humanspeak/svelte-json-view-lite.git"
27
+ },
28
+ "funding": {
29
+ "type": "github",
30
+ "url": "https://github.com/sponsors/humanspeak"
31
+ },
32
+ "license": "MIT",
33
+ "author": "Humanspeak, Inc.",
34
+ "sideEffects": [
35
+ "**/*.css"
36
+ ],
37
+ "type": "module",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "svelte": "./dist/index.js"
42
+ }
43
+ },
44
+ "svelte": "./dist/index.js",
45
+ "types": "./dist/index.d.ts",
46
+ "files": [
47
+ "dist",
48
+ "!dist/**/*.test.*",
49
+ "!dist/**/*.spec.*",
50
+ "!dist/test/**/*"
51
+ ],
52
+ "overrides": {
53
+ "@sveltejs/kit": {
54
+ "cookie": "^0.7.0"
55
+ }
56
+ },
57
+ "devDependencies": {
58
+ "@eslint/compat": "^2.0.5",
59
+ "@eslint/js": "^10.0.1",
60
+ "@playwright/test": "^1.59.1",
61
+ "@sveltejs/adapter-auto": "^7.0.1",
62
+ "@sveltejs/kit": "^2.57.1",
63
+ "@sveltejs/package": "^2.5.7",
64
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
65
+ "@testing-library/jest-dom": "^6.9.1",
66
+ "@testing-library/svelte": "^5.3.1",
67
+ "@testing-library/user-event": "^14.6.1",
68
+ "@types/node": "^25.6.0",
69
+ "@typescript-eslint/eslint-plugin": "^8.58.2",
70
+ "@typescript-eslint/parser": "^8.58.2",
71
+ "@vitest/coverage-v8": "^4.1.4",
72
+ "eslint": "^10.2.1",
73
+ "eslint-config-prettier": "^10.1.8",
74
+ "eslint-plugin-import": "^2.32.0",
75
+ "eslint-plugin-svelte": "^3.17.0",
76
+ "eslint-plugin-unused-imports": "^4.4.1",
77
+ "globals": "^17.5.0",
78
+ "husky": "^9.1.7",
79
+ "jsdom": "^29.0.2",
80
+ "mprocs": "^0.9.2",
81
+ "prettier": "^3.8.3",
82
+ "prettier-plugin-organize-imports": "^4.3.0",
83
+ "prettier-plugin-svelte": "^3.5.1",
84
+ "publint": "^0.3.18",
85
+ "svelte": "^5.55.4",
86
+ "svelte-check": "^4.4.6",
87
+ "typescript": "^6.0.3",
88
+ "typescript-eslint": "^8.58.2",
89
+ "vite": "^8.0.8",
90
+ "vitest": "^4.1.4"
91
+ },
92
+ "peerDependencies": {
93
+ "svelte": "^5.0.0"
94
+ },
95
+ "volta": {
96
+ "node": "24.15.0"
97
+ },
98
+ "publishConfig": {
99
+ "access": "public"
100
+ },
101
+ "tags": [
102
+ "svelte",
103
+ "json",
104
+ "viewer"
105
+ ],
106
+ "scripts": {
107
+ "build": "vite build && npm run package",
108
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
109
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
110
+ "dev": "vite dev",
111
+ "dev:all": "mprocs",
112
+ "dev:pkg": "svelte-kit sync && svelte-package --watch",
113
+ "format": "prettier --write .",
114
+ "lint": "prettier --check . && eslint .",
115
+ "lint:fix": "npm run format && eslint . --fix",
116
+ "package": "svelte-kit sync && svelte-package && publint",
117
+ "preview": "vite preview",
118
+ "test": "vitest run --coverage",
119
+ "test:all": "npm run test && npm run test:e2e",
120
+ "test:e2e": "playwright test",
121
+ "test:e2e:debug": "playwright test --debug",
122
+ "test:e2e:report": "playwright show-report",
123
+ "test:e2e:ui": "playwright test --ui",
124
+ "test:only": "vitest run",
125
+ "test:watch": "vitest"
126
+ }
127
+ }