@atom.io/spa-devtools 0.0.0-stage → 0.0.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 +373 -0
- package/README.md +51 -2
- package/README.mdx +35 -0
- package/dist/css.d.ts +2 -0
- package/dist/devtools.css +289 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +904 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -5
- package/src/AtomIODevtools.tsx +194 -0
- package/src/Indexes.tsx +331 -0
- package/src/JsonEditor.tsx +259 -0
- package/src/css.d.ts +2 -0
- package/src/devtools.css +289 -0
- package/src/index.ts +2 -0
- package/src/store.ts +148 -0
- package/src/value.ts +79 -0
package/src/devtools.css
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
main[data-css="spa-devtools"] {
|
|
2
|
+
--fg-color: #ccc;
|
|
3
|
+
--fg-light: #aaa;
|
|
4
|
+
--fg-soft: #888;
|
|
5
|
+
--fg-hint: #4a4a4a;
|
|
6
|
+
--fg-max: #fff;
|
|
7
|
+
--bg-color: #111;
|
|
8
|
+
--bg-accent: #00f;
|
|
9
|
+
--bg-max: #000;
|
|
10
|
+
--bg-tint1: #222;
|
|
11
|
+
--bg-tint2: #333;
|
|
12
|
+
color-scheme: dark;
|
|
13
|
+
@media (prefers-color-scheme: light) {
|
|
14
|
+
--fg-color: #444;
|
|
15
|
+
--fg-light: #777;
|
|
16
|
+
--fg-soft: #888;
|
|
17
|
+
--fg-hint: #ccc;
|
|
18
|
+
--fg-max: #000;
|
|
19
|
+
--bg-color: #ddd;
|
|
20
|
+
--bg-accent: #0ff;
|
|
21
|
+
--bg-max: #fff;
|
|
22
|
+
--bg-tint1: #e3e3e3;
|
|
23
|
+
--bg-tint2: #f3f3f3;
|
|
24
|
+
color-scheme: light;
|
|
25
|
+
}
|
|
26
|
+
position: fixed;
|
|
27
|
+
z-index: 10000;
|
|
28
|
+
right: 0;
|
|
29
|
+
bottom: 0;
|
|
30
|
+
width: min(500px, 100vw);
|
|
31
|
+
height: min(800px, 100dvh);
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
border: 2px solid var(--fg-color);
|
|
36
|
+
background: var(--bg-color);
|
|
37
|
+
color: var(--fg-color);
|
|
38
|
+
font: 14px/1 Theia, monospace;
|
|
39
|
+
text-align: left;
|
|
40
|
+
&, * {
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
}
|
|
43
|
+
* {
|
|
44
|
+
color: inherit;
|
|
45
|
+
font: inherit;
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
}
|
|
49
|
+
&[hidden] {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
&[data-open="false"] {
|
|
53
|
+
width: 33px;
|
|
54
|
+
height: 28px;
|
|
55
|
+
border-color: transparent;
|
|
56
|
+
background: transparent;
|
|
57
|
+
.toolbar > :not([data-testid="open-close-devtools"]) {
|
|
58
|
+
display: none;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
button, input, select {
|
|
62
|
+
border-radius: 0;
|
|
63
|
+
background: none;
|
|
64
|
+
min-width: 0;
|
|
65
|
+
}
|
|
66
|
+
button {
|
|
67
|
+
height: 21px;
|
|
68
|
+
min-width: 21px;
|
|
69
|
+
border: 0;
|
|
70
|
+
padding: 0 3px;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
}
|
|
73
|
+
button:hover {
|
|
74
|
+
background: var(--fg-hint);
|
|
75
|
+
}
|
|
76
|
+
button:disabled {
|
|
77
|
+
cursor: default;
|
|
78
|
+
color: var(--fg-soft);
|
|
79
|
+
}
|
|
80
|
+
button:focus-visible, input:focus-visible, select:focus-visible {
|
|
81
|
+
outline: 2px solid var(--fg-max);
|
|
82
|
+
outline-offset: -2px;
|
|
83
|
+
background: var(--bg-max);
|
|
84
|
+
color: var(--fg-max);
|
|
85
|
+
}
|
|
86
|
+
input {
|
|
87
|
+
height: 18px;
|
|
88
|
+
padding: 0 2px;
|
|
89
|
+
border: 1px solid var(--fg-hint);
|
|
90
|
+
}
|
|
91
|
+
input[readonly] {
|
|
92
|
+
border-color: transparent;
|
|
93
|
+
}
|
|
94
|
+
input::selection {
|
|
95
|
+
background: var(--bg-accent);
|
|
96
|
+
}
|
|
97
|
+
input[type="checkbox"] {
|
|
98
|
+
width: 13px;
|
|
99
|
+
height: 13px;
|
|
100
|
+
margin: 0 3px;
|
|
101
|
+
}
|
|
102
|
+
select {
|
|
103
|
+
height: 20px;
|
|
104
|
+
border: 0;
|
|
105
|
+
color: var(--fg-soft);
|
|
106
|
+
flex: 0 0 auto;
|
|
107
|
+
max-width: 76px;
|
|
108
|
+
}
|
|
109
|
+
header, nav, footer {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 0;
|
|
113
|
+
}
|
|
114
|
+
.toolbar {
|
|
115
|
+
flex: 0 0 28px;
|
|
116
|
+
min-width: 0;
|
|
117
|
+
}
|
|
118
|
+
.toolbar h1 {
|
|
119
|
+
font: 24px/1 Charter, serif;
|
|
120
|
+
margin: 0 4px;
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
}
|
|
123
|
+
.toolbar nav {
|
|
124
|
+
margin-left: auto;
|
|
125
|
+
align-self: end;
|
|
126
|
+
overflow-x: auto;
|
|
127
|
+
}
|
|
128
|
+
.toolbar nav button {
|
|
129
|
+
height: 24px;
|
|
130
|
+
padding: 0 5px;
|
|
131
|
+
}
|
|
132
|
+
.toolbar nav button[aria-pressed="true"] {
|
|
133
|
+
background: var(--fg-color);
|
|
134
|
+
color: var(--bg-color);
|
|
135
|
+
}
|
|
136
|
+
.drag-handle {
|
|
137
|
+
touch-action: none;
|
|
138
|
+
cursor: move;
|
|
139
|
+
min-width: 16px;
|
|
140
|
+
color: var(--fg-soft);
|
|
141
|
+
}
|
|
142
|
+
.index {
|
|
143
|
+
flex: 1;
|
|
144
|
+
min-height: 0;
|
|
145
|
+
overflow: auto;
|
|
146
|
+
border-top: 1px solid var(--fg-color);
|
|
147
|
+
background: var(--bg-tint1);
|
|
148
|
+
padding-bottom: 24px;
|
|
149
|
+
}
|
|
150
|
+
.index > p {
|
|
151
|
+
padding: 5px;
|
|
152
|
+
font-style: italic;
|
|
153
|
+
color: var(--fg-soft);
|
|
154
|
+
}
|
|
155
|
+
.node {
|
|
156
|
+
border-bottom: 1px solid var(--fg-soft);
|
|
157
|
+
}
|
|
158
|
+
.node > header, .event > header {
|
|
159
|
+
height: 22px;
|
|
160
|
+
background: var(--bg-tint2);
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
overflow: hidden;
|
|
163
|
+
}
|
|
164
|
+
.node > header h2 {
|
|
165
|
+
padding: 0 3px;
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
text-overflow: ellipsis;
|
|
168
|
+
}
|
|
169
|
+
.node > header small {
|
|
170
|
+
margin-right: auto;
|
|
171
|
+
}
|
|
172
|
+
small {
|
|
173
|
+
color: var(--fg-soft);
|
|
174
|
+
}
|
|
175
|
+
.node > .state-leaf ~ .state-leaf {
|
|
176
|
+
border-top: 1px solid var(--fg-hint);
|
|
177
|
+
}
|
|
178
|
+
.node:has(> header) > .state-leaf {
|
|
179
|
+
margin-left: 9px;
|
|
180
|
+
border-left: 1px solid var(--fg-soft);
|
|
181
|
+
}
|
|
182
|
+
.json-editor {
|
|
183
|
+
min-width: 0;
|
|
184
|
+
}
|
|
185
|
+
.json-editor > header {
|
|
186
|
+
height: 21px;
|
|
187
|
+
min-width: 0;
|
|
188
|
+
background: var(--bg-tint2);
|
|
189
|
+
white-space: nowrap;
|
|
190
|
+
}
|
|
191
|
+
.state-leaf > .json-editor > header {
|
|
192
|
+
height: 22px;
|
|
193
|
+
}
|
|
194
|
+
.carat-spacer {
|
|
195
|
+
width: 21px;
|
|
196
|
+
flex-shrink: 0;
|
|
197
|
+
}
|
|
198
|
+
.field-name {
|
|
199
|
+
padding-right: 4px;
|
|
200
|
+
flex: 0 1 auto;
|
|
201
|
+
min-width: 0;
|
|
202
|
+
overflow: hidden;
|
|
203
|
+
text-overflow: ellipsis;
|
|
204
|
+
}
|
|
205
|
+
.field-name h3 {
|
|
206
|
+
font-weight: inherit;
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
text-overflow: ellipsis;
|
|
209
|
+
}
|
|
210
|
+
.field-name:empty {
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
.field-name input {
|
|
214
|
+
max-width: 100%;
|
|
215
|
+
}
|
|
216
|
+
.json-editor > header > code {
|
|
217
|
+
flex: 1;
|
|
218
|
+
min-width: 0;
|
|
219
|
+
overflow: hidden;
|
|
220
|
+
text-overflow: ellipsis;
|
|
221
|
+
color: var(--fg-light);
|
|
222
|
+
padding: 0 3px;
|
|
223
|
+
}
|
|
224
|
+
.primitive {
|
|
225
|
+
flex: 1;
|
|
226
|
+
min-width: 0;
|
|
227
|
+
padding: 0 2px;
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
}
|
|
230
|
+
.primitive input[type="text"] {
|
|
231
|
+
width: 100%;
|
|
232
|
+
}
|
|
233
|
+
.primitive code {
|
|
234
|
+
white-space: nowrap;
|
|
235
|
+
}
|
|
236
|
+
.properties, .impact {
|
|
237
|
+
margin-left: 9px;
|
|
238
|
+
border-left: 1px solid var(--fg-soft);
|
|
239
|
+
}
|
|
240
|
+
.properties > .property {
|
|
241
|
+
border-top: 1px solid var(--fg-hint);
|
|
242
|
+
}
|
|
243
|
+
.property .field-name::after {
|
|
244
|
+
content: ":";
|
|
245
|
+
color: var(--fg-soft);
|
|
246
|
+
padding-right: 3px;
|
|
247
|
+
}
|
|
248
|
+
.properties > footer {
|
|
249
|
+
height: 21px;
|
|
250
|
+
border-top: 1px solid var(--fg-hint);
|
|
251
|
+
}
|
|
252
|
+
.properties > footer button {
|
|
253
|
+
color: var(--fg-soft);
|
|
254
|
+
}
|
|
255
|
+
.event {
|
|
256
|
+
border-top: 1px solid var(--fg-hint);
|
|
257
|
+
}
|
|
258
|
+
.event > header strong {
|
|
259
|
+
padding-right: 5px;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
text-overflow: ellipsis;
|
|
262
|
+
}
|
|
263
|
+
.event p, .event > code {
|
|
264
|
+
display: block;
|
|
265
|
+
padding: 3px 6px;
|
|
266
|
+
line-height: 18px;
|
|
267
|
+
overflow-wrap: anywhere;
|
|
268
|
+
}
|
|
269
|
+
ins {
|
|
270
|
+
color: var(--fg-color);
|
|
271
|
+
text-decoration: none;
|
|
272
|
+
background: #0a02;
|
|
273
|
+
}
|
|
274
|
+
del {
|
|
275
|
+
color: var(--fg-soft);
|
|
276
|
+
background: #a002;
|
|
277
|
+
}
|
|
278
|
+
.you-are-here {
|
|
279
|
+
background: var(--fg-color);
|
|
280
|
+
color: var(--bg-color);
|
|
281
|
+
text-align: center;
|
|
282
|
+
line-height: 21px;
|
|
283
|
+
}
|
|
284
|
+
[role="alert"] {
|
|
285
|
+
padding: 5px;
|
|
286
|
+
line-height: 18px;
|
|
287
|
+
border-top: 1px solid var(--fg-soft);
|
|
288
|
+
}
|
|
289
|
+
}
|
package/src/index.ts
ADDED
package/src/store.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { Accessor } from "@atom.io/spa"
|
|
2
|
+
import { onCleanup, useId, useSetter, useValue } from "@atom.io/spa"
|
|
3
|
+
import type { RegularAtomFamilyToken, RegularAtomToken, Setter } from "atom.io"
|
|
4
|
+
import type { RootStore } from "atom.io/internal"
|
|
5
|
+
import {
|
|
6
|
+
createRegularAtomFamily,
|
|
7
|
+
disposeFromStore,
|
|
8
|
+
findInStore,
|
|
9
|
+
setIntoStore,
|
|
10
|
+
} from "atom.io/internal"
|
|
11
|
+
import type { IntrospectionStates } from "atom.io/introspection"
|
|
12
|
+
import { attachIntrospectionStates } from "atom.io/introspection"
|
|
13
|
+
|
|
14
|
+
const locals = new WeakMap<RootStore, RegularAtomFamilyToken<unknown, string>>()
|
|
15
|
+
const introspection = new WeakMap<RootStore, IntrospectionStates>()
|
|
16
|
+
export type Path = readonly string[]
|
|
17
|
+
export interface Tools extends IntrospectionStates {
|
|
18
|
+
store: RootStore
|
|
19
|
+
expanded: RegularAtomToken<Record<string, boolean>>
|
|
20
|
+
error: RegularAtomToken<string>
|
|
21
|
+
attempt(run: () => void): void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ownedAtom<T>(store: RootStore, initial: T): RegularAtomToken<T> {
|
|
25
|
+
let family = locals.get(store)
|
|
26
|
+
if (!family) {
|
|
27
|
+
family = createRegularAtomFamily<unknown, string, never>(store, {
|
|
28
|
+
key: `🔍 spa devtools`,
|
|
29
|
+
default: undefined,
|
|
30
|
+
})
|
|
31
|
+
locals.set(store, family)
|
|
32
|
+
}
|
|
33
|
+
const token = findInStore(store, family, useId()) as RegularAtomToken<T>
|
|
34
|
+
setIntoStore(store, token, initial)
|
|
35
|
+
onCleanup(() => {
|
|
36
|
+
disposeFromStore(store, token)
|
|
37
|
+
})
|
|
38
|
+
return token
|
|
39
|
+
}
|
|
40
|
+
export function useLocal<T>(
|
|
41
|
+
store: RootStore,
|
|
42
|
+
initial: T,
|
|
43
|
+
): [Accessor<T>, (next: T | Setter<T>) => void] {
|
|
44
|
+
const token = ownedAtom(store, initial)
|
|
45
|
+
return [useValue(token) as Accessor<T>, useSetter(token)]
|
|
46
|
+
}
|
|
47
|
+
export function useSaved<T>(
|
|
48
|
+
store: RootStore,
|
|
49
|
+
key: string,
|
|
50
|
+
fallback: T,
|
|
51
|
+
): [Accessor<T>, (next: T | Setter<T>) => void] {
|
|
52
|
+
let initial = fallback
|
|
53
|
+
try {
|
|
54
|
+
const saved: unknown = JSON.parse(localStorage.getItem(key) ?? `null`)
|
|
55
|
+
if (saved !== null && typeof saved === typeof fallback) initial = saved as T
|
|
56
|
+
} catch {
|
|
57
|
+
/* Storage may be unavailable. */
|
|
58
|
+
}
|
|
59
|
+
const [value, set] = useLocal(store, initial)
|
|
60
|
+
return [
|
|
61
|
+
value,
|
|
62
|
+
(next) => {
|
|
63
|
+
set(next)
|
|
64
|
+
try {
|
|
65
|
+
localStorage.setItem(key, JSON.stringify(value()))
|
|
66
|
+
} catch {
|
|
67
|
+
/* Keep in-memory state when storage is unavailable. */
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
export function useTools(store: RootStore): Tools {
|
|
73
|
+
let states = introspection.get(store)
|
|
74
|
+
if (!states) {
|
|
75
|
+
states = attachIntrospectionStates(store)
|
|
76
|
+
introspection.set(store, states)
|
|
77
|
+
}
|
|
78
|
+
let expanded: Record<string, boolean> = {}
|
|
79
|
+
try {
|
|
80
|
+
const saved: unknown = JSON.parse(
|
|
81
|
+
localStorage.getItem(`🔍 spa expanded`) ?? `{}`,
|
|
82
|
+
)
|
|
83
|
+
if (
|
|
84
|
+
saved &&
|
|
85
|
+
typeof saved === `object` &&
|
|
86
|
+
!Array.isArray(saved) &&
|
|
87
|
+
Object.values(saved).every((value) => typeof value === `boolean`)
|
|
88
|
+
)
|
|
89
|
+
expanded = saved as Record<string, boolean>
|
|
90
|
+
} catch {
|
|
91
|
+
/* Ignore corrupt or unavailable storage. */
|
|
92
|
+
}
|
|
93
|
+
const error = ownedAtom(store, ``)
|
|
94
|
+
const setError = useSetter(error)
|
|
95
|
+
return {
|
|
96
|
+
...states,
|
|
97
|
+
store,
|
|
98
|
+
error,
|
|
99
|
+
expanded: ownedAtom(store, expanded),
|
|
100
|
+
attempt(run) {
|
|
101
|
+
try {
|
|
102
|
+
run()
|
|
103
|
+
setError(``)
|
|
104
|
+
} catch (caught) {
|
|
105
|
+
setError(caught instanceof Error ? caught.message : String(caught))
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function useExpanded(
|
|
111
|
+
tools: Tools,
|
|
112
|
+
path: Path,
|
|
113
|
+
initial = false,
|
|
114
|
+
): [Accessor<boolean>, (event: MouseEvent) => void] {
|
|
115
|
+
const value = useValue(tools.expanded)
|
|
116
|
+
const set = useSetter(tools.expanded)
|
|
117
|
+
const key = JSON.stringify(path)
|
|
118
|
+
const group = `all:${JSON.stringify(path.slice(0, -1))}`
|
|
119
|
+
const read = () => value()[key] ?? value()[group] ?? initial
|
|
120
|
+
return [
|
|
121
|
+
read,
|
|
122
|
+
(event) => {
|
|
123
|
+
const next = !read()
|
|
124
|
+
set((previous) => {
|
|
125
|
+
const result = { ...previous }
|
|
126
|
+
if (event.shiftKey) {
|
|
127
|
+
const parent = JSON.stringify(path.slice(0, -1))
|
|
128
|
+
for (const item of Object.keys(result)) {
|
|
129
|
+
if (item.startsWith(`[`)) {
|
|
130
|
+
const candidate = JSON.parse(item) as string[]
|
|
131
|
+
if (JSON.stringify(candidate.slice(0, -1)) === parent)
|
|
132
|
+
Reflect.deleteProperty(result, item)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
result[group] = next
|
|
136
|
+
} else {
|
|
137
|
+
result[key] = next
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
localStorage.setItem(`🔍 spa expanded`, JSON.stringify(result))
|
|
141
|
+
} catch {
|
|
142
|
+
/* Keep in-memory expansion state. */
|
|
143
|
+
}
|
|
144
|
+
return result
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
}
|
package/src/value.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type Change = (update: (previous: unknown) => unknown) => void
|
|
2
|
+
export type JsonKind =
|
|
3
|
+
| `string`
|
|
4
|
+
| `number`
|
|
5
|
+
| `boolean`
|
|
6
|
+
| `null`
|
|
7
|
+
| `array`
|
|
8
|
+
| `object`
|
|
9
|
+
export function kind(value: unknown): JsonKind | `other` {
|
|
10
|
+
if (value === null) return `null`
|
|
11
|
+
if (Array.isArray(value)) return `array`
|
|
12
|
+
if (
|
|
13
|
+
typeof value === `object` &&
|
|
14
|
+
(Object.getPrototypeOf(value) === Object.prototype ||
|
|
15
|
+
Object.getPrototypeOf(value) === null)
|
|
16
|
+
)
|
|
17
|
+
return `object`
|
|
18
|
+
if (
|
|
19
|
+
typeof value === `string` ||
|
|
20
|
+
typeof value === `number` ||
|
|
21
|
+
typeof value === `boolean`
|
|
22
|
+
)
|
|
23
|
+
return typeof value as JsonKind
|
|
24
|
+
return `other`
|
|
25
|
+
}
|
|
26
|
+
export function describe(value: unknown): string {
|
|
27
|
+
try {
|
|
28
|
+
const seen = new WeakSet<object>()
|
|
29
|
+
return (
|
|
30
|
+
JSON.stringify(value, (_, item: unknown) => {
|
|
31
|
+
if (typeof item === `bigint`) return `${item}n`
|
|
32
|
+
if (item instanceof Error) return `${item.name}: ${item.message}`
|
|
33
|
+
if (item instanceof Map) return { Map: [...item] }
|
|
34
|
+
if (item instanceof Set) return { Set: [...item] }
|
|
35
|
+
if (typeof item === `object` && item !== null) {
|
|
36
|
+
if (seen.has(item)) return `[Circular]`
|
|
37
|
+
seen.add(item)
|
|
38
|
+
}
|
|
39
|
+
return item
|
|
40
|
+
}) ?? String(value)
|
|
41
|
+
)
|
|
42
|
+
} catch {
|
|
43
|
+
return Object.prototype.toString.call(value)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function convert(value: unknown, type: JsonKind): unknown {
|
|
47
|
+
switch (type) {
|
|
48
|
+
case `null`:
|
|
49
|
+
return null
|
|
50
|
+
case `string`:
|
|
51
|
+
return value == null
|
|
52
|
+
? ``
|
|
53
|
+
: typeof value === `string`
|
|
54
|
+
? value
|
|
55
|
+
: describe(value)
|
|
56
|
+
case `number`: {
|
|
57
|
+
const next = Array.isArray(value) ? value.length : Number(value)
|
|
58
|
+
return Number.isFinite(next) ? next : 0
|
|
59
|
+
}
|
|
60
|
+
case `boolean`:
|
|
61
|
+
return typeof value === `string` ? value === `true` : Boolean(value)
|
|
62
|
+
case `array`:
|
|
63
|
+
return Array.isArray(value)
|
|
64
|
+
? value
|
|
65
|
+
: value == null
|
|
66
|
+
? []
|
|
67
|
+
: typeof value === `object`
|
|
68
|
+
? Object.entries(value)
|
|
69
|
+
: [value]
|
|
70
|
+
case `object`:
|
|
71
|
+
return kind(value) === `object`
|
|
72
|
+
? value
|
|
73
|
+
: Array.isArray(value)
|
|
74
|
+
? Object.fromEntries(value.map((item, index) => [index, item]))
|
|
75
|
+
: value == null
|
|
76
|
+
? {}
|
|
77
|
+
: { value }
|
|
78
|
+
}
|
|
79
|
+
}
|