polen 0.10.0-next.21 → 0.10.0-next.23

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 (94) hide show
  1. package/build/api/builder/builder.d.ts +4 -11
  2. package/build/api/builder/builder.d.ts.map +1 -1
  3. package/build/api/builder/builder.js +4 -3
  4. package/build/api/builder/builder.js.map +1 -1
  5. package/build/api/config/configurator.d.ts +62 -11
  6. package/build/api/config/configurator.d.ts.map +1 -1
  7. package/build/api/config/configurator.js +9 -0
  8. package/build/api/config/configurator.js.map +1 -1
  9. package/build/api/config/merge.d.ts.map +1 -1
  10. package/build/api/config/merge.js +8 -0
  11. package/build/api/config/merge.js.map +1 -1
  12. package/build/api/vite/plugins/core.d.ts.map +1 -1
  13. package/build/api/vite/plugins/core.js +1 -0
  14. package/build/api/vite/plugins/core.js.map +1 -1
  15. package/build/cli/commands/build.js +11 -7
  16. package/build/cli/commands/build.js.map +1 -1
  17. package/build/project-data.d.ts +1 -0
  18. package/build/project-data.d.ts.map +1 -1
  19. package/build/sandbox.js +40 -17
  20. package/build/sandbox.js.map +1 -1
  21. package/build/template/components/CodeBlock.d.ts.map +1 -1
  22. package/build/template/components/CodeBlock.js +3 -5
  23. package/build/template/components/CodeBlock.js.map +1 -1
  24. package/build/template/components/Field.js +1 -1
  25. package/build/template/components/Field.js.map +1 -1
  26. package/build/template/components/GraphQLInteractive/GraphQLInteractive.d.ts +31 -0
  27. package/build/template/components/GraphQLInteractive/GraphQLInteractive.d.ts.map +1 -0
  28. package/build/template/components/GraphQLInteractive/GraphQLInteractive.js +275 -0
  29. package/build/template/components/GraphQLInteractive/GraphQLInteractive.js.map +1 -0
  30. package/build/template/components/GraphQLInteractive/components/GraphQLErrorBoundary.d.ts +39 -0
  31. package/build/template/components/GraphQLInteractive/components/GraphQLErrorBoundary.d.ts.map +1 -0
  32. package/build/template/components/GraphQLInteractive/components/GraphQLErrorBoundary.js +51 -0
  33. package/build/template/components/GraphQLInteractive/components/GraphQLErrorBoundary.js.map +1 -0
  34. package/build/template/components/GraphQLInteractive/components/GraphQLTokenPopover.d.ts +33 -0
  35. package/build/template/components/GraphQLInteractive/components/GraphQLTokenPopover.d.ts.map +1 -0
  36. package/build/template/components/GraphQLInteractive/components/GraphQLTokenPopover.js +242 -0
  37. package/build/template/components/GraphQLInteractive/components/GraphQLTokenPopover.js.map +1 -0
  38. package/build/template/components/GraphQLInteractive/hooks/use-popover-state.d.ts +45 -0
  39. package/build/template/components/GraphQLInteractive/hooks/use-popover-state.d.ts.map +1 -0
  40. package/build/template/components/GraphQLInteractive/hooks/use-popover-state.js +176 -0
  41. package/build/template/components/GraphQLInteractive/hooks/use-popover-state.js.map +1 -0
  42. package/build/template/components/GraphQLInteractive/index.d.ts +2 -0
  43. package/build/template/components/GraphQLInteractive/index.d.ts.map +1 -0
  44. package/build/template/components/GraphQLInteractive/index.js +2 -0
  45. package/build/template/components/GraphQLInteractive/index.js.map +1 -0
  46. package/build/template/components/GraphQLInteractive/lib/graphql-node-types.d.ts +52 -0
  47. package/build/template/components/GraphQLInteractive/lib/graphql-node-types.d.ts.map +1 -0
  48. package/build/template/components/GraphQLInteractive/lib/graphql-node-types.js +34 -0
  49. package/build/template/components/GraphQLInteractive/lib/graphql-node-types.js.map +1 -0
  50. package/build/template/components/GraphQLInteractive/lib/parser.d.ts +71 -0
  51. package/build/template/components/GraphQLInteractive/lib/parser.d.ts.map +1 -0
  52. package/build/template/components/GraphQLInteractive/lib/parser.js +836 -0
  53. package/build/template/components/GraphQLInteractive/lib/parser.js.map +1 -0
  54. package/build/template/components/GraphQLInteractive/lib/semantic-nodes.d.ts +98 -0
  55. package/build/template/components/GraphQLInteractive/lib/semantic-nodes.d.ts.map +1 -0
  56. package/build/template/components/GraphQLInteractive/lib/semantic-nodes.js +31 -0
  57. package/build/template/components/GraphQLInteractive/lib/semantic-nodes.js.map +1 -0
  58. package/build/template/components/content/$$.d.ts +0 -1
  59. package/build/template/components/content/$$.d.ts.map +1 -1
  60. package/build/template/components/content/$$.js +0 -1
  61. package/build/template/components/content/$$.js.map +1 -1
  62. package/package.json +5 -21
  63. package/src/api/builder/builder.ts +8 -13
  64. package/src/api/config/configurator.ts +72 -11
  65. package/src/api/config/merge.ts +13 -0
  66. package/src/api/vite/plugins/core.ts +1 -0
  67. package/src/cli/commands/build.ts +11 -7
  68. package/src/lib/kit-temp.test.ts +9 -9
  69. package/src/project-data.ts +1 -0
  70. package/src/sandbox.ts +40 -17
  71. package/src/template/components/CodeBlock.tsx +6 -9
  72. package/src/template/components/Field.tsx +1 -1
  73. package/src/template/components/GraphQLInteractive/GraphQLInteractive.tsx +464 -0
  74. package/src/template/components/GraphQLInteractive/components/GraphQLErrorBoundary.tsx +96 -0
  75. package/src/template/components/GraphQLInteractive/components/GraphQLTokenPopover.tsx +492 -0
  76. package/src/template/components/GraphQLInteractive/hooks/use-popover-state.ts +244 -0
  77. package/src/template/components/GraphQLInteractive/index.ts +1 -0
  78. package/src/template/components/GraphQLInteractive/lib/graphql-node-types.ts +217 -0
  79. package/src/template/components/GraphQLInteractive/lib/parser.ts +1075 -0
  80. package/src/template/components/GraphQLInteractive/lib/semantic-nodes.ts +154 -0
  81. package/src/template/components/GraphQLInteractive/tests/parser-comment.test.ts +33 -0
  82. package/src/template/components/GraphQLInteractive/tests/parser-error-hint.test.ts +102 -0
  83. package/src/template/components/GraphQLInteractive/tests/parser.test.ts +131 -0
  84. package/src/template/components/content/$$.ts +0 -1
  85. package/build/template/components/content/GraphQLDocumentWithSchema.d.ts +0 -8
  86. package/build/template/components/content/GraphQLDocumentWithSchema.d.ts.map +0 -1
  87. package/build/template/components/content/GraphQLDocumentWithSchema.js +0 -13
  88. package/build/template/components/content/GraphQLDocumentWithSchema.js.map +0 -1
  89. package/build/template/components/content/GraphQLDocumentWrapper.d.ts +0 -7
  90. package/build/template/components/content/GraphQLDocumentWrapper.d.ts.map +0 -1
  91. package/build/template/components/content/GraphQLDocumentWrapper.js +0 -48
  92. package/build/template/components/content/GraphQLDocumentWrapper.js.map +0 -1
  93. package/src/template/components/content/GraphQLDocumentWithSchema.tsx +0 -13
  94. package/src/template/components/content/GraphQLDocumentWrapper.tsx +0 -72
@@ -0,0 +1,492 @@
1
+ /**
2
+ * Popover component for GraphQL tokens
3
+ *
4
+ * Provides rich overlays with type information that can be triggered by hover
5
+ * and pinned by clicking. Supports multiple simultaneous popovers.
6
+ */
7
+
8
+ import type { React } from '#dep/react/index'
9
+ import { Cross1Icon } from '@radix-ui/react-icons'
10
+ import { Button, Popover } from '@radix-ui/themes'
11
+ import type { GraphQLToken } from '../lib/parser.js'
12
+ import {
13
+ isArgument,
14
+ isFragment,
15
+ isInputField,
16
+ isInvalidField,
17
+ isOperation,
18
+ isOutputField,
19
+ } from '../lib/semantic-nodes.js'
20
+
21
+ interface GraphQLTokenPopoverProps {
22
+ /** The token to create a popover for */
23
+ token: GraphQLToken
24
+
25
+ /** Child element that triggers the popover */
26
+ children: React.ReactNode
27
+
28
+ /** Whether the popover is open */
29
+ open: boolean
30
+
31
+ /** Whether the popover is pinned */
32
+ pinned: boolean
33
+
34
+ /** Called when mouse enters the trigger */
35
+ onTriggerHover: () => void
36
+
37
+ /** Called when mouse leaves the trigger */
38
+ onTriggerLeave: () => void
39
+
40
+ /** Called when trigger is clicked */
41
+ onTriggerClick: (e: React.MouseEvent) => void
42
+
43
+ /** Called when popover content is hovered */
44
+ onContentHover: () => void
45
+
46
+ /** Called when mouse leaves popover content */
47
+ onContentLeave: () => void
48
+
49
+ /** Called when close button is clicked */
50
+ onClose: () => void
51
+ }
52
+
53
+ /**
54
+ * Get rich content for a token popover
55
+ */
56
+ function getPopoverContent(
57
+ token: GraphQLToken,
58
+ ): {
59
+ title: string
60
+ path?: Array<{ name: string; url: string | null }>
61
+ description?: string
62
+ type?: string
63
+ invalid?: boolean
64
+ availableFields?: string[]
65
+ typeName?: string
66
+ typeUrl?: string
67
+ fieldName?: string
68
+ } | null {
69
+ const semantic = token.semantic
70
+
71
+ if (!semantic) return null
72
+
73
+ if (isInvalidField(semantic)) {
74
+ // Get available fields from the parent type
75
+ const availableFields = Object.keys(semantic.parentType.getFields()).sort()
76
+
77
+ // TODO: In the future, check other schema versions to see if the field existed previously
78
+ // This would help identify renamed or removed fields across schema evolution
79
+
80
+ return {
81
+ title: `Field "${semantic.fieldName}" does not exist`,
82
+ description: `Type ${semantic.parentType.name} does not have a field named "${semantic.fieldName}".`,
83
+ typeName: semantic.parentType.name,
84
+ typeUrl: `/reference/${semantic.parentType.name}`,
85
+ fieldName: semantic.fieldName,
86
+ invalid: true,
87
+ availableFields,
88
+ }
89
+ }
90
+
91
+ if (isOutputField(semantic)) {
92
+ const fieldType = semantic.fieldDef.type.toString()
93
+ return {
94
+ title: semantic.fieldDef.name,
95
+ path: [
96
+ { name: semantic.parentType.name, url: `/reference/${semantic.parentType.name}` },
97
+ { name: semantic.fieldDef.name, url: `/reference/${semantic.parentType.name}#${semantic.fieldDef.name}` },
98
+ ],
99
+ description: semantic.fieldDef.description || undefined,
100
+ type: `${fieldType}`,
101
+ }
102
+ }
103
+
104
+ if (isInputField(semantic)) {
105
+ const fieldType = semantic.fieldDef.type.toString()
106
+ return {
107
+ title: semantic.fieldDef.name,
108
+ path: [
109
+ { name: semantic.parentType.name, url: `/reference/${semantic.parentType.name}` },
110
+ { name: semantic.fieldDef.name, url: `/reference/${semantic.parentType.name}#${semantic.fieldDef.name}` },
111
+ ],
112
+ description: semantic.fieldDef.description || undefined,
113
+ type: `${fieldType}`,
114
+ }
115
+ }
116
+
117
+ if (isArgument(semantic)) {
118
+ const argType = semantic.argumentDef.type.toString()
119
+ return {
120
+ title: semantic.argumentDef.name,
121
+ path: [
122
+ { name: semantic.parentType.name, url: `/reference/${semantic.parentType.name}` },
123
+ { name: semantic.parentField.name, url: `/reference/${semantic.parentType.name}#${semantic.parentField.name}` },
124
+ {
125
+ name: semantic.argumentDef.name,
126
+ url: `/reference/${semantic.parentType.name}#${semantic.parentField.name}__${semantic.argumentDef.name}`,
127
+ },
128
+ ],
129
+ description: semantic.argumentDef.description || undefined,
130
+ type: `${argType}`,
131
+ }
132
+ }
133
+
134
+ if (isOperation(semantic)) {
135
+ return {
136
+ title: semantic.name || `${semantic.type} operation`,
137
+ path: [
138
+ { name: semantic.name || semantic.type, url: null },
139
+ ],
140
+ description: 'GraphQL Operation',
141
+ type: semantic.type.charAt(0).toUpperCase() + semantic.type.slice(1),
142
+ }
143
+ }
144
+
145
+ if (isFragment(semantic)) {
146
+ return {
147
+ title: semantic.name,
148
+ path: [
149
+ { name: 'fragment', url: null },
150
+ { name: semantic.name, url: null },
151
+ ],
152
+ description: 'GraphQL Fragment',
153
+ type: `Fragment on ${semantic.onType?.name || 'Unknown'}`,
154
+ }
155
+ }
156
+
157
+ if ('name' in semantic && semantic.name) {
158
+ // Type reference
159
+ return {
160
+ title: semantic.name,
161
+ path: [
162
+ { name: semantic.name, url: `/reference/${semantic.name}` },
163
+ ],
164
+ description: ('description' in semantic && typeof semantic.description === 'string')
165
+ ? semantic.description
166
+ : undefined,
167
+ type: 'GraphQL Type',
168
+ }
169
+ }
170
+
171
+ return null
172
+ }
173
+
174
+ export const GraphQLTokenPopover: React.FC<GraphQLTokenPopoverProps> = ({
175
+ token,
176
+ children,
177
+ open,
178
+ pinned,
179
+ onTriggerHover,
180
+ onTriggerLeave,
181
+ onTriggerClick,
182
+ onContentHover,
183
+ onContentLeave,
184
+ onClose,
185
+ }) => {
186
+ const content = getPopoverContent(token)
187
+
188
+ // If no content, just render children without popover
189
+ if (!content) {
190
+ return <>{children}</>
191
+ }
192
+
193
+ const popoverContent = (
194
+ <div
195
+ style={{
196
+ padding: 'var(--space-1)',
197
+ minWidth: '200px',
198
+ maxWidth: '400px',
199
+ position: 'relative',
200
+ }}
201
+ onMouseEnter={onContentHover}
202
+ onMouseLeave={onContentLeave}
203
+ >
204
+ {/* Header with close button (if pinned) */}
205
+ <div
206
+ style={{
207
+ display: 'flex',
208
+ justifyContent: 'space-between',
209
+ alignItems: 'flex-start',
210
+ marginBottom: '6px',
211
+ }}
212
+ >
213
+ <div style={{ flex: 1 }}>
214
+ {/* Path with clickable links - not shown for invalid fields */}
215
+ {content.path && !content.invalid && (
216
+ <div
217
+ style={{
218
+ fontSize: 'var(--font-size-2)',
219
+ color: 'var(--gray-12)',
220
+ marginBottom: 'var(--space-1)',
221
+ }}
222
+ >
223
+ {content.path.map((segment, index) => (
224
+ <span key={index}>
225
+ {index > 0 && <span style={{ color: 'var(--gray-11)', margin: '0 var(--space-1)' }}>.</span>}
226
+ {segment.url
227
+ ? (
228
+ <a
229
+ href={segment.url}
230
+ style={{
231
+ color: '#0969da',
232
+ textDecoration: 'none',
233
+ cursor: 'pointer',
234
+ }}
235
+ onMouseEnter={(e) => {
236
+ e.currentTarget.style.textDecoration = 'underline'
237
+ }}
238
+ onMouseLeave={(e) => {
239
+ e.currentTarget.style.textDecoration = 'none'
240
+ }}
241
+ onClick={(e) => {
242
+ e.preventDefault()
243
+ if (segment.url) {
244
+ window.location.href = segment.url
245
+ }
246
+ }}
247
+ >
248
+ {segment.name}
249
+ </a>
250
+ )
251
+ : (
252
+ <span style={{ color: '#24292e' }}>
253
+ {segment.name}
254
+ </span>
255
+ )}
256
+ </span>
257
+ ))}
258
+ </div>
259
+ )}
260
+
261
+ {/* Type info for valid fields */}
262
+ {content.type && !content.invalid && (
263
+ <div
264
+ style={{
265
+ fontSize: '12px',
266
+ color: '#6a737d',
267
+ fontFamily: 'monospace',
268
+ opacity: 0.8,
269
+ }}
270
+ >
271
+ {content.type}
272
+ </div>
273
+ )}
274
+
275
+ {/* Error message for invalid fields */}
276
+ {content.invalid && (
277
+ <div
278
+ style={{
279
+ fontSize: '14px',
280
+ color: '#d73a49',
281
+ marginBottom: '4px',
282
+ }}
283
+ >
284
+ <span style={{ fontWeight: 'bold' }}>Error:</span> Type{' '}
285
+ <a
286
+ href={content.typeUrl}
287
+ style={{
288
+ color: '#0969da',
289
+ textDecoration: 'none',
290
+ cursor: 'pointer',
291
+ }}
292
+ onMouseEnter={(e) => {
293
+ e.currentTarget.style.textDecoration = 'underline'
294
+ }}
295
+ onMouseLeave={(e) => {
296
+ e.currentTarget.style.textDecoration = 'none'
297
+ }}
298
+ onClick={(e) => {
299
+ e.preventDefault()
300
+ if (content.typeUrl) {
301
+ window.location.href = content.typeUrl
302
+ }
303
+ }}
304
+ >
305
+ {content.typeName}
306
+ </a>{' '}
307
+ does not have a field named "{content.fieldName}".
308
+ </div>
309
+ )}
310
+ </div>
311
+
312
+ {pinned && !content.invalid && (
313
+ <Button
314
+ variant='ghost'
315
+ size='1'
316
+ onClick={onClose}
317
+ style={{
318
+ marginLeft: '8px',
319
+ padding: '2px',
320
+ minWidth: 'auto',
321
+ height: 'auto',
322
+ }}
323
+ >
324
+ <Cross1Icon width='12' height='12' />
325
+ </Button>
326
+ )}
327
+ </div>
328
+
329
+ {/* Description for valid fields only */}
330
+ {content.description && !content.invalid && (
331
+ <div
332
+ style={{
333
+ fontSize: '13px',
334
+ color: '#24292e',
335
+ lineHeight: '1.4',
336
+ opacity: 0.9,
337
+ }}
338
+ >
339
+ {content.description}
340
+ </div>
341
+ )}
342
+
343
+ {/* Available fields in SDL-like format */}
344
+ {content.invalid && content.availableFields && (
345
+ <div
346
+ style={{
347
+ marginTop: '8px',
348
+ backgroundColor: '#f6f8fa',
349
+ border: '1px solid #e1e4e8',
350
+ borderRadius: '4px',
351
+ fontSize: '12px',
352
+ fontFamily: 'monospace',
353
+ lineHeight: '1.5',
354
+ overflow: 'hidden',
355
+ }}
356
+ >
357
+ <div style={{ padding: '10px 10px 0 10px' }}>
358
+ <span style={{ color: '#d73a49' }}>type</span>{' '}
359
+ <a
360
+ href={content.typeUrl}
361
+ style={{
362
+ color: '#005cc5',
363
+ fontWeight: 500,
364
+ textDecoration: 'none',
365
+ }}
366
+ onMouseEnter={(e) => {
367
+ e.currentTarget.style.textDecoration = 'underline'
368
+ }}
369
+ onMouseLeave={(e) => {
370
+ e.currentTarget.style.textDecoration = 'none'
371
+ }}
372
+ >
373
+ {content.typeName}
374
+ </a>{' '}
375
+ <span style={{ color: '#24292e' }}>{'{'}</span>
376
+ </div>
377
+ <div
378
+ style={{
379
+ maxHeight: '200px',
380
+ overflowY: 'auto',
381
+ padding: '0 10px',
382
+ marginRight: '2px', // Account for scrollbar
383
+ }}
384
+ >
385
+ {(() => {
386
+ // Insert the invalid field in alphabetical position
387
+ const invalidFieldName = content.fieldName || ''
388
+ const allFields = [...content.availableFields]
389
+ const insertIndex = allFields.findIndex(f => f.localeCompare(invalidFieldName) > 0)
390
+
391
+ // Create the full list with invalid field inserted
392
+ const fieldsWithInvalid = insertIndex === -1
393
+ ? [...allFields, invalidFieldName]
394
+ : [...allFields.slice(0, insertIndex), invalidFieldName, ...allFields.slice(insertIndex)]
395
+
396
+ return fieldsWithInvalid.map((field) => {
397
+ const isInvalidField = field === invalidFieldName
398
+ return (
399
+ <div
400
+ key={field}
401
+ style={{
402
+ paddingLeft: isInvalidField ? '26px' : '16px',
403
+ color: isInvalidField ? '#d73a49' : '#6f42c1',
404
+ display: 'flex',
405
+ alignItems: 'center',
406
+ backgroundColor: isInvalidField ? 'var(--red-a2)' : 'transparent',
407
+ marginLeft: isInvalidField ? '-10px' : '0',
408
+ marginRight: isInvalidField ? '-10px' : '0',
409
+ paddingTop: isInvalidField ? '2px' : '0',
410
+ paddingBottom: isInvalidField ? '2px' : '0',
411
+ }}
412
+ >
413
+ {isInvalidField
414
+ ? (
415
+ <span style={{ textDecoration: 'line-through' }}>
416
+ {field}
417
+ </span>
418
+ )
419
+ : (
420
+ <a
421
+ href={`/reference/${content.typeName}#${field}`}
422
+ style={{
423
+ color: '#6f42c1',
424
+ textDecoration: 'none',
425
+ }}
426
+ onMouseEnter={(e) => {
427
+ e.currentTarget.style.textDecoration = 'underline'
428
+ }}
429
+ onMouseLeave={(e) => {
430
+ e.currentTarget.style.textDecoration = 'none'
431
+ }}
432
+ >
433
+ {field}
434
+ </a>
435
+ )}
436
+ {isInvalidField && (
437
+ <span
438
+ style={{
439
+ marginLeft: '12px',
440
+ color: '#d73a49',
441
+ fontSize: '11px',
442
+ display: 'flex',
443
+ alignItems: 'center',
444
+ gap: '4px',
445
+ }}
446
+ >
447
+ ← No such field
448
+ </span>
449
+ )}
450
+ </div>
451
+ )
452
+ })
453
+ })()}
454
+ </div>
455
+ <div style={{ padding: '0 10px 10px 10px', color: '#24292e' }}>{'}'}</div>
456
+ </div>
457
+ )}
458
+ </div>
459
+ )
460
+
461
+ return (
462
+ <Popover.Root open={open}>
463
+ <Popover.Trigger>
464
+ <span
465
+ style={{ cursor: token.polen.isInteractive() ? 'pointer' : 'default' }}
466
+ onMouseEnter={onTriggerHover}
467
+ onMouseLeave={onTriggerLeave}
468
+ onClick={onTriggerClick}
469
+ >
470
+ {children}
471
+ </span>
472
+ </Popover.Trigger>
473
+
474
+ <Popover.Content
475
+ side='top'
476
+ align='start'
477
+ sideOffset={4}
478
+ style={{
479
+ padding: 'var(--space-2)',
480
+ background: content.invalid ? 'var(--red-1)' : 'var(--color-background)',
481
+ border: content.invalid ? '2px solid var(--red-7)' : '1px solid var(--gray-6)',
482
+ borderRadius: 'var(--radius-1)',
483
+ boxShadow: 'none',
484
+ zIndex: 1000,
485
+ maxWidth: '450px',
486
+ }}
487
+ >
488
+ {popoverContent}
489
+ </Popover.Content>
490
+ </Popover.Root>
491
+ )
492
+ }