@tanstack/query-devtools 5.0.0-alpha.27

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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/build/.tsbuildinfo +1 -0
  3. package/build/cjs/index.js +7966 -0
  4. package/build/cjs/index.js.map +1 -0
  5. package/build/esm/index.js +7964 -0
  6. package/build/esm/index.js.map +1 -0
  7. package/build/source/Context.js +10 -0
  8. package/build/source/Devtools.jsx +1485 -0
  9. package/build/source/Explorer.jsx +266 -0
  10. package/build/source/__tests__/devtools.test.jsx +6 -0
  11. package/build/source/fonts.js +7 -0
  12. package/build/source/icons/index.jsx +344 -0
  13. package/build/source/index.jsx +64 -0
  14. package/build/source/theme.js +304 -0
  15. package/build/source/utils.jsx +74 -0
  16. package/build/types/Context.d.ts +29 -0
  17. package/build/types/Context.d.ts.map +1 -0
  18. package/build/types/Devtools.d.ts +23 -0
  19. package/build/types/Devtools.d.ts.map +1 -0
  20. package/build/types/Explorer.d.ts +22 -0
  21. package/build/types/Explorer.d.ts.map +1 -0
  22. package/build/types/__tests__/devtools.test.d.ts +1 -0
  23. package/build/types/__tests__/devtools.test.d.ts.map +1 -0
  24. package/build/types/fonts.d.ts +2 -0
  25. package/build/types/fonts.d.ts.map +1 -0
  26. package/build/types/icons/index.d.ts +12 -0
  27. package/build/types/icons/index.d.ts.map +1 -0
  28. package/build/types/index.d.ts +27 -0
  29. package/build/types/index.d.ts.map +1 -0
  30. package/build/types/theme.d.ts +297 -0
  31. package/build/types/theme.d.ts.map +1 -0
  32. package/build/types/utils.d.ts +22 -0
  33. package/build/types/utils.d.ts.map +1 -0
  34. package/build/umd/index.js +872 -0
  35. package/build/umd/index.js.map +1 -0
  36. package/package.json +53 -0
  37. package/src/Context.ts +41 -0
  38. package/src/Devtools.tsx +1890 -0
  39. package/src/Explorer.tsx +348 -0
  40. package/src/__tests__/devtools.test.tsx +5 -0
  41. package/src/fonts.ts +7 -0
  42. package/src/icons/index.tsx +1065 -0
  43. package/src/index.tsx +113 -0
  44. package/src/theme.ts +321 -0
  45. package/src/utils.tsx +103 -0
@@ -0,0 +1,348 @@
1
+ import { displayValue } from './utils'
2
+ import superjson from 'superjson'
3
+ import { css, cx } from '@emotion/css'
4
+ import { tokens } from './theme'
5
+ import { createMemo, createSignal, Index, Match, Show, Switch } from 'solid-js'
6
+ import { Key } from '@solid-primitives/keyed'
7
+ import { CopiedCopier, Copier, ErrorCopier } from './icons'
8
+
9
+ /**
10
+ * Chunk elements in the array by size
11
+ *
12
+ * when the array cannot be chunked evenly by size, the last chunk will be
13
+ * filled with the remaining elements
14
+ *
15
+ * @example
16
+ * chunkArray(['a','b', 'c', 'd', 'e'], 2) // returns [['a','b'], ['c', 'd'], ['e']]
17
+ */
18
+ export function chunkArray<T extends { label: string; value: unknown }>(
19
+ array: T[],
20
+ size: number,
21
+ ): T[][] {
22
+ if (size < 1) return []
23
+ let i = 0
24
+ const result: T[][] = []
25
+ while (i < array.length) {
26
+ result.push(array.slice(i, i + size))
27
+ i = i + size
28
+ }
29
+ return result
30
+ }
31
+
32
+ const Expander = (props: { expanded: boolean }) => {
33
+ const styles = getStyles()
34
+
35
+ return (
36
+ <span
37
+ class={cx(
38
+ styles.expander,
39
+ css`
40
+ transform: rotate(${props.expanded ? 90 : 0}deg);
41
+ `,
42
+ )}
43
+ >
44
+ <svg
45
+ width="16"
46
+ height="16"
47
+ viewBox="0 0 16 16"
48
+ fill="none"
49
+ xmlns="http://www.w3.org/2000/svg"
50
+ >
51
+ <path
52
+ d="M6 12L10 8L6 4"
53
+ stroke-width="2"
54
+ stroke-linecap="round"
55
+ stroke-linejoin="round"
56
+ />
57
+ </svg>
58
+ </span>
59
+ )
60
+ }
61
+
62
+ type CopyState = 'NoCopy' | 'SuccessCopy' | 'ErrorCopy'
63
+ const CopyButton = (props: { value: unknown }) => {
64
+ const styles = getStyles()
65
+ const [copyState, setCopyState] = createSignal<CopyState>('NoCopy')
66
+
67
+ return (
68
+ <button
69
+ class={styles.copyButton}
70
+ aria-label={`${
71
+ copyState() === 'NoCopy'
72
+ ? 'Copy object to clipboard'
73
+ : copyState() === 'SuccessCopy'
74
+ ? 'Object copied to clipboard'
75
+ : 'Error copying object to clipboard'
76
+ }`}
77
+ onClick={
78
+ copyState() === 'NoCopy'
79
+ ? () => {
80
+ navigator.clipboard
81
+ .writeText(superjson.stringify(props.value))
82
+ .then(
83
+ () => {
84
+ setCopyState('SuccessCopy')
85
+ setTimeout(() => {
86
+ setCopyState('NoCopy')
87
+ }, 1500)
88
+ },
89
+ (err) => {
90
+ console.error('Failed to copy: ', err)
91
+ setCopyState('ErrorCopy')
92
+ setTimeout(() => {
93
+ setCopyState('NoCopy')
94
+ }, 1500)
95
+ },
96
+ )
97
+ }
98
+ : undefined
99
+ }
100
+ >
101
+ <Switch>
102
+ <Match when={copyState() === 'NoCopy'}>
103
+ <Copier />
104
+ </Match>
105
+ <Match when={copyState() === 'SuccessCopy'}>
106
+ <CopiedCopier />
107
+ </Match>
108
+ <Match when={copyState() === 'ErrorCopy'}>
109
+ <ErrorCopier />
110
+ </Match>
111
+ </Switch>
112
+ </button>
113
+ )
114
+ }
115
+
116
+ type ExplorerProps = {
117
+ copyable?: boolean
118
+ label: string
119
+ value: unknown
120
+ defaultExpanded?: string[]
121
+ }
122
+
123
+ function isIterable(x: any): x is Iterable<unknown> {
124
+ return Symbol.iterator in x
125
+ }
126
+
127
+ export default function Explorer(props: ExplorerProps) {
128
+ const styles = getStyles()
129
+
130
+ const [expanded, setExpanded] = createSignal(
131
+ (props.defaultExpanded || []).includes(props.label),
132
+ )
133
+ const toggleExpanded = () => setExpanded((old) => !old)
134
+ const [expandedPages, setExpandedPages] = createSignal<number[]>([])
135
+
136
+ const subEntries = createMemo(() => {
137
+ if (Array.isArray(props.value)) {
138
+ return props.value.map((d, i) => ({
139
+ label: i.toString(),
140
+ value: d,
141
+ }))
142
+ } else if (
143
+ props.value !== null &&
144
+ typeof props.value === 'object' &&
145
+ isIterable(props.value) &&
146
+ typeof props.value[Symbol.iterator] === 'function'
147
+ ) {
148
+ if (props.value instanceof Map) {
149
+ return Array.from(props.value, ([key, val]) => ({
150
+ label: key,
151
+ value: val,
152
+ }))
153
+ }
154
+ return Array.from(props.value, (val, i) => ({
155
+ label: i.toString(),
156
+ value: val,
157
+ }))
158
+ } else if (typeof props.value === 'object' && props.value !== null) {
159
+ return Object.entries(props.value).map(([key, val]) => ({
160
+ label: key,
161
+ value: val,
162
+ }))
163
+ }
164
+ return []
165
+ })
166
+
167
+ const type = createMemo<string>(() => {
168
+ if (Array.isArray(props.value)) {
169
+ return 'array'
170
+ } else if (
171
+ props.value !== null &&
172
+ typeof props.value === 'object' &&
173
+ isIterable(props.value) &&
174
+ typeof props.value[Symbol.iterator] === 'function'
175
+ ) {
176
+ return 'Iterable'
177
+ } else if (typeof props.value === 'object' && props.value !== null) {
178
+ return 'object'
179
+ }
180
+ return typeof props.value
181
+ })
182
+
183
+ const subEntryPages = createMemo(() => chunkArray(subEntries(), 100))
184
+
185
+ return (
186
+ <div class={styles.entry}>
187
+ <Show when={subEntryPages().length}>
188
+ <button class={styles.expanderButton} onClick={() => toggleExpanded()}>
189
+ <Expander expanded={expanded()} /> <span>{props.label}</span>{' '}
190
+ <span class={styles.info}>
191
+ {String(type()).toLowerCase() === 'iterable' ? '(Iterable) ' : ''}
192
+ {subEntries().length} {subEntries().length > 1 ? `items` : `item`}
193
+ </span>
194
+ </button>
195
+ <Show when={props.copyable}>
196
+ <CopyButton value={props.value} />
197
+ </Show>
198
+ <Show when={expanded()}>
199
+ <Show when={subEntryPages().length === 1}>
200
+ <div class={styles.subEntry}>
201
+ <Key each={subEntries()} by={(item) => item.label}>
202
+ {(entry) => {
203
+ return (
204
+ <Explorer
205
+ defaultExpanded={props.defaultExpanded}
206
+ label={entry().label}
207
+ value={entry().value}
208
+ copyable={props.copyable}
209
+ />
210
+ )
211
+ }}
212
+ </Key>
213
+ </div>
214
+ </Show>
215
+ <Show when={subEntryPages().length > 1}>
216
+ <div class={styles.subEntry}>
217
+ <Index each={subEntryPages()}>
218
+ {(entries, index) => (
219
+ <div>
220
+ <div class={styles.entry}>
221
+ <button
222
+ onClick={() =>
223
+ setExpandedPages((old) =>
224
+ old.includes(index)
225
+ ? old.filter((d) => d !== index)
226
+ : [...old, index],
227
+ )
228
+ }
229
+ class={styles.expanderButton}
230
+ >
231
+ <Expander expanded={expandedPages().includes(index)} />{' '}
232
+ [{index * 100}...
233
+ {index * 100 + 100 - 1}]
234
+ </button>
235
+ <Show when={expandedPages().includes(index)}>
236
+ <div class={styles.subEntry}>
237
+ <Key each={entries()} by={(entry) => entry.label}>
238
+ {(entry) => (
239
+ <Explorer
240
+ defaultExpanded={props.defaultExpanded}
241
+ label={entry().label}
242
+ value={entry().value}
243
+ copyable={props.copyable}
244
+ />
245
+ )}
246
+ </Key>
247
+ </div>
248
+ </Show>
249
+ </div>
250
+ </div>
251
+ )}
252
+ </Index>
253
+ </div>
254
+ </Show>
255
+ </Show>
256
+ </Show>
257
+ <Show when={subEntryPages().length === 0}>
258
+ <span class={styles.label}>{props.label}:</span>{' '}
259
+ <span class={styles.value}>{displayValue(props.value)}</span>
260
+ </Show>
261
+ </div>
262
+ )
263
+ }
264
+
265
+ const getStyles = () => {
266
+ const { colors, font, size, border } = tokens
267
+
268
+ return {
269
+ entry: css`
270
+ & * {
271
+ font-size: ${font.size.sm};
272
+ font-family: 'Menlo', 'Fira Code', monospace;
273
+ line-height: 1.7;
274
+ }
275
+ position: relative;
276
+ outline: none;
277
+ word-break: break-word;
278
+ `,
279
+ subEntry: css`
280
+ margin: 0 0 0 0.5em;
281
+ padding-left: 0.75em;
282
+ border-left: 2px solid ${colors.darkGray[400]};
283
+ `,
284
+ expander: css`
285
+ & path {
286
+ stroke: ${colors.gray[400]};
287
+ }
288
+ display: inline-flex;
289
+ align-items: center;
290
+ transition: all 0.1s ease;
291
+ `,
292
+ expanderButton: css`
293
+ cursor: pointer;
294
+ color: inherit;
295
+ font: inherit;
296
+ outline: inherit;
297
+ line-height: ${font.size.sm};
298
+ background: transparent;
299
+ border: none;
300
+ padding: 0;
301
+ display: inline-flex;
302
+ align-items: center;
303
+ gap: ${size[1]};
304
+
305
+ &:focus-visible {
306
+ border-radius: ${border.radius.xs};
307
+ outline: 2px solid ${colors.blue[800]};
308
+ }
309
+ `,
310
+ info: css`
311
+ color: ${colors.gray[500]};
312
+ font-size: ${font.size.xs};
313
+ line-height: ${font.size.xs};
314
+ margin-left: ${size[1]};
315
+ `,
316
+ label: css`
317
+ color: ${colors.gray[300]};
318
+ `,
319
+ value: css`
320
+ color: ${colors.purple[400]};
321
+ `,
322
+ copyButton: css`
323
+ background-color: transparent;
324
+ border: none;
325
+ display: inline-flex;
326
+ padding: 0px;
327
+ align-items: center;
328
+ justify-content: center;
329
+ cursor: pointer;
330
+ width: ${size[3.5]};
331
+ height: ${size[3.5]};
332
+ position: relative;
333
+ top: 4px;
334
+ left: ${size[2]};
335
+ z-index: 1;
336
+
337
+ &:hover svg .copier {
338
+ stroke: ${colors.gray[500]} !important;
339
+ }
340
+
341
+ &:focus-visible {
342
+ border-radius: ${border.radius.xs};
343
+ outline: 2px solid ${colors.blue[800]};
344
+ outline-offset: 2px;
345
+ }
346
+ `,
347
+ }
348
+ }
@@ -0,0 +1,5 @@
1
+ describe('ReactQueryDevtools', () => {
2
+ it('should be able to open and close devtools', async () => {
3
+ expect(1).toBe(1)
4
+ })
5
+ })
package/src/fonts.ts ADDED
@@ -0,0 +1,7 @@
1
+ export const loadFonts = () => {
2
+ const link = document.createElement('link')
3
+ link.href =
4
+ 'https://fonts.googleapis.com/css2?family=Fira+Code&family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Roboto&display=swap'
5
+ link.rel = 'stylesheet'
6
+ document.head.appendChild(link)
7
+ }