polen 0.10.0-next.12 → 0.10.0-next.13

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 (42) hide show
  1. package/build/lib/graphql-document/components/CopyButton.d.ts +19 -0
  2. package/build/lib/graphql-document/components/CopyButton.d.ts.map +1 -0
  3. package/build/lib/graphql-document/components/CopyButton.js +43 -0
  4. package/build/lib/graphql-document/components/CopyButton.js.map +1 -0
  5. package/build/lib/graphql-document/components/GraphQLDocument.d.ts +0 -4
  6. package/build/lib/graphql-document/components/GraphQLDocument.d.ts.map +1 -1
  7. package/build/lib/graphql-document/components/GraphQLDocument.js +31 -74
  8. package/build/lib/graphql-document/components/GraphQLDocument.js.map +1 -1
  9. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.d.ts +33 -0
  10. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.d.ts.map +1 -0
  11. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.js +48 -0
  12. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.js.map +1 -0
  13. package/build/lib/graphql-document/components/IdentifierLink.d.ts +15 -13
  14. package/build/lib/graphql-document/components/IdentifierLink.d.ts.map +1 -1
  15. package/build/lib/graphql-document/components/IdentifierLink.js +51 -117
  16. package/build/lib/graphql-document/components/IdentifierLink.js.map +1 -1
  17. package/build/lib/graphql-document/components/graphql-document-styles.d.ts +5 -0
  18. package/build/lib/graphql-document/components/graphql-document-styles.d.ts.map +1 -0
  19. package/build/lib/graphql-document/components/graphql-document-styles.js +167 -0
  20. package/build/lib/graphql-document/components/graphql-document-styles.js.map +1 -0
  21. package/build/lib/graphql-document/components/index.d.ts +2 -1
  22. package/build/lib/graphql-document/components/index.d.ts.map +1 -1
  23. package/build/lib/graphql-document/components/index.js +2 -1
  24. package/build/lib/graphql-document/components/index.js.map +1 -1
  25. package/build/lib/graphql-document/hooks/use-tooltip-state.d.ts +43 -0
  26. package/build/lib/graphql-document/hooks/use-tooltip-state.d.ts.map +1 -0
  27. package/build/lib/graphql-document/hooks/use-tooltip-state.js +132 -0
  28. package/build/lib/graphql-document/hooks/use-tooltip-state.js.map +1 -0
  29. package/package.json +2 -1
  30. package/src/lib/graphql-document/components/CopyButton.tsx +76 -0
  31. package/src/lib/graphql-document/components/GraphQLDocument.tsx +52 -86
  32. package/src/lib/graphql-document/components/GraphQLIdentifierPopover.tsx +197 -0
  33. package/src/lib/graphql-document/components/IdentifierLink.tsx +105 -166
  34. package/src/lib/graphql-document/components/graphql-document-styles.ts +167 -0
  35. package/src/lib/graphql-document/components/index.ts +2 -1
  36. package/src/lib/graphql-document/hooks/use-tooltip-state.test.ts +76 -0
  37. package/src/lib/graphql-document/hooks/use-tooltip-state.ts +191 -0
  38. package/build/lib/graphql-document/components/HoverTooltip.d.ts +0 -35
  39. package/build/lib/graphql-document/components/HoverTooltip.d.ts.map +0 -1
  40. package/build/lib/graphql-document/components/HoverTooltip.js +0 -132
  41. package/build/lib/graphql-document/components/HoverTooltip.js.map +0 -1
  42. package/src/lib/graphql-document/components/HoverTooltip.tsx +0 -282
@@ -1,13 +1,13 @@
1
+ /**
2
+ * Interactive overlay for GraphQL identifiers
3
+ */
4
+
1
5
  import type { React } from '#dep/react/index'
2
- import { useState } from 'react'
3
6
  import type { DOMPosition } from '../positioning-simple.ts'
4
7
  import type { SchemaResolution } from '../schema-integration.ts'
5
8
  import type { Identifier } from '../types.ts'
6
- import { HoverTooltip } from './HoverTooltip.tsx'
9
+ import { GraphQLIdentifierPopover } from './GraphQLIdentifierPopover.tsx'
7
10
 
8
- /**
9
- * Props for the IdentifierLink component
10
- */
11
11
  export interface IdentifierLinkProps {
12
12
  /** The GraphQL identifier */
13
13
  identifier: Identifier
@@ -20,16 +20,24 @@ export interface IdentifierLinkProps {
20
20
  /** Whether to show debug visuals */
21
21
  debug?: boolean
22
22
  /** Whether this tooltip is open */
23
- isOpen?: boolean
24
- /** Toggle tooltip open state */
25
- onToggle?: (open: boolean) => void
23
+ isOpen: boolean
24
+ /** Whether this tooltip is pinned */
25
+ isPinned: boolean
26
+ /** Handle hover start */
27
+ onHoverStart: () => void
28
+ /** Handle hover end */
29
+ onHoverEnd: () => void
30
+ /** Toggle pin state */
31
+ onTogglePin: () => void
32
+ /** Handle tooltip hover */
33
+ onTooltipHover: () => void
26
34
  }
27
35
 
28
36
  /**
29
37
  * Interactive overlay for a GraphQL identifier
30
38
  *
31
39
  * Renders an invisible clickable area over the identifier text
32
- * with hover tooltips and navigation to schema reference pages.
40
+ * with hover popovers and navigation to schema reference pages.
33
41
  */
34
42
  export const IdentifierLink: React.FC<IdentifierLinkProps> = ({
35
43
  identifier,
@@ -37,17 +45,13 @@ export const IdentifierLink: React.FC<IdentifierLinkProps> = ({
37
45
  position,
38
46
  onNavigate,
39
47
  debug = false,
40
- isOpen = false,
41
- onToggle,
48
+ isOpen,
49
+ isPinned,
50
+ onHoverStart,
51
+ onHoverEnd,
52
+ onTogglePin,
53
+ onTooltipHover,
42
54
  }) => {
43
- const [isHovered, setIsHovered] = useState(false)
44
-
45
- // Use external state if provided, otherwise manage locally
46
- const showTooltip = isOpen
47
- const setShowTooltip = (show: boolean) => {
48
- onToggle?.(show)
49
- }
50
-
51
55
  // Determine visual state
52
56
  const isClickable = resolution.exists
53
57
  const hasError = !resolution.exists && (identifier.kind === 'Type' || identifier.kind === 'Field')
@@ -60,162 +64,97 @@ export const IdentifierLink: React.FC<IdentifierLinkProps> = ({
60
64
  isClickable && 'graphql-clickable',
61
65
  hasError && 'graphql-error',
62
66
  isDeprecated && 'graphql-deprecated',
63
- isHovered && 'graphql-hovered',
64
- showTooltip && 'graphql-tooltip-open',
67
+ isOpen && 'graphql-hovered',
68
+ isOpen && 'graphql-tooltip-open',
65
69
  debug && 'graphql-debug',
66
70
  ].filter(Boolean).join(' ')
67
71
 
68
72
  const handleClick = (e: React.MouseEvent) => {
69
73
  e.preventDefault()
70
74
  e.stopPropagation()
71
-
72
- // Toggle tooltip on click
73
- setShowTooltip(!showTooltip)
75
+ onTogglePin()
74
76
  }
75
77
 
76
- const handleNavigate = (e: React.MouseEvent) => {
77
- e.preventDefault()
78
- e.stopPropagation()
79
- if (isClickable) {
80
- onNavigate(resolution.referenceUrl)
81
- }
82
- }
78
+ // Create trigger element
79
+ const triggerElement = isClickable
80
+ ? (
81
+ <a
82
+ href={resolution.referenceUrl}
83
+ className={classNames + ' graphql-identifier-link'}
84
+ style={{
85
+ position: 'absolute',
86
+ top: position.top,
87
+ left: position.left,
88
+ width: position.width,
89
+ height: position.height,
90
+ cursor: 'pointer',
91
+ zIndex: 10,
92
+ pointerEvents: 'auto',
93
+ display: 'block',
94
+ textDecoration: 'none',
95
+ // Debug mode visual
96
+ ...(debug && {
97
+ backgroundColor: hasError ? 'rgba(239, 68, 68, 0.1)' : 'rgba(59, 130, 246, 0.1)',
98
+ border: `1px solid ${hasError ? 'rgba(239, 68, 68, 0.3)' : 'rgba(59, 130, 246, 0.3)'}`,
99
+ }),
100
+ }}
101
+ onClick={handleClick}
102
+ onMouseEnter={onHoverStart}
103
+ onMouseLeave={onHoverEnd}
104
+ aria-label={`${identifier.kind} ${identifier.name} - Click to view documentation`}
105
+ data-graphql-identifier={identifier.name}
106
+ data-graphql-kind={identifier.kind}
107
+ />
108
+ )
109
+ : (
110
+ <div
111
+ className={classNames}
112
+ style={{
113
+ position: 'absolute',
114
+ top: position.top,
115
+ left: position.left,
116
+ width: position.width,
117
+ height: position.height,
118
+ cursor: 'pointer', // Make it clickable even for errors
119
+ zIndex: 10,
120
+ pointerEvents: 'auto',
121
+ // Debug mode visual
122
+ ...(debug && {
123
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
124
+ border: `1px solid rgba(239, 68, 68, 0.3)`,
125
+ }),
126
+ }}
127
+ onClick={handleClick}
128
+ onMouseEnter={onHoverStart}
129
+ onMouseLeave={onHoverEnd}
130
+ role='button'
131
+ aria-label={`${identifier.kind} ${identifier.name} - Click to view information`}
132
+ data-graphql-identifier={identifier.name}
133
+ data-graphql-kind={identifier.kind}
134
+ />
135
+ )
83
136
 
84
137
  return (
85
- <>
86
- {isClickable
87
- ? (
88
- <a
89
- href={resolution.referenceUrl}
90
- className={classNames + ' graphql-identifier-link'}
91
- style={{
92
- position: 'absolute',
93
- top: position.top,
94
- left: position.left,
95
- width: position.width,
96
- height: position.height,
97
- cursor: 'pointer',
98
- zIndex: 10,
99
- pointerEvents: 'auto',
100
- display: 'block',
101
- textDecoration: 'none',
102
- // Debug mode visual
103
- ...(debug && {
104
- backgroundColor: hasError ? 'rgba(239, 68, 68, 0.1)' : 'rgba(59, 130, 246, 0.1)',
105
- border: `1px solid ${hasError ? 'rgba(239, 68, 68, 0.3)' : 'rgba(59, 130, 246, 0.3)'}`,
106
- }),
107
- }}
108
- onClick={handleClick}
109
- onMouseEnter={() => setIsHovered(true)}
110
- onMouseLeave={() => setIsHovered(false)}
111
- aria-label={`${identifier.kind} ${identifier.name} - Click to view documentation`}
112
- data-graphql-identifier={identifier.name}
113
- data-graphql-kind={identifier.kind}
114
- />
115
- )
116
- : (
117
- <div
118
- className={classNames}
119
- style={{
120
- position: 'absolute',
121
- top: position.top,
122
- left: position.left,
123
- width: position.width,
124
- height: position.height,
125
- cursor: 'pointer', // Make it clickable even for errors
126
- zIndex: 10,
127
- pointerEvents: 'auto',
128
- // Debug mode visual
129
- ...(debug && {
130
- backgroundColor: 'rgba(239, 68, 68, 0.1)',
131
- border: `1px solid rgba(239, 68, 68, 0.3)`,
132
- }),
133
- }}
134
- onClick={handleClick} // Add click handler for errors too
135
- onMouseEnter={() => setIsHovered(true)}
136
- onMouseLeave={() => setIsHovered(false)}
137
- role='button'
138
- aria-label={`${identifier.kind} ${identifier.name} - Click to view information`}
139
- data-graphql-identifier={identifier.name}
140
- data-graphql-kind={identifier.kind}
141
- />
142
- )}
143
-
144
- {/* Tooltip - show on click, not hover */}
145
- {showTooltip && (
146
- <HoverTooltip
147
- identifier={identifier}
148
- documentation={resolution.documentation || {
149
- description: hasError
150
- ? `${identifier.kind} "${identifier.name}" not found in schema. This ${identifier.kind.toLowerCase()} does not exist in the current GraphQL schema.`
151
- : `${identifier.kind}: ${identifier.name}`,
152
- typeInfo: identifier.kind,
153
- }}
154
- position={position}
155
- hasError={hasError}
156
- referenceUrl={resolution.referenceUrl}
157
- onClose={() => setShowTooltip(false)}
158
- onNavigate={isClickable ? () => onNavigate(resolution.referenceUrl) : undefined}
159
- />
160
- )}
161
- </>
138
+ <GraphQLIdentifierPopover
139
+ identifier={identifier}
140
+ documentation={resolution.documentation || {
141
+ description: hasError
142
+ ? `${identifier.kind} "${identifier.name}" not found in schema. This ${identifier.kind.toLowerCase()} does not exist in the current GraphQL schema.`
143
+ : `${identifier.kind}: ${identifier.name}`,
144
+ typeInfo: identifier.kind,
145
+ }}
146
+ hasError={hasError}
147
+ referenceUrl={resolution.referenceUrl}
148
+ open={isOpen}
149
+ isPinned={isPinned}
150
+ onOpenChange={(open) => {
151
+ if (!open && isPinned) {
152
+ onTogglePin() // Unpin when closing
153
+ }
154
+ }}
155
+ onNavigate={isClickable ? onNavigate : undefined}
156
+ >
157
+ {triggerElement}
158
+ </GraphQLIdentifierPopover>
162
159
  )
163
160
  }
164
-
165
- /**
166
- * Default styles for identifier overlays
167
- *
168
- * These can be included in the document or overridden by custom styles.
169
- */
170
- export const identifierLinkStyles = `
171
- .graphql-identifier-overlay {
172
- /* Base styles for all overlays */
173
- transition: background-color 0.2s ease;
174
- }
175
-
176
- .graphql-identifier-overlay.graphql-clickable:hover {
177
- /* Subtle highlight on hover for clickable identifiers */
178
- background-color: rgba(59, 130, 246, 0.05);
179
- }
180
-
181
- .graphql-identifier-overlay.graphql-error {
182
- /* Error indicator */
183
- border-bottom: 2px wavy red;
184
- }
185
-
186
- .graphql-identifier-overlay.graphql-deprecated {
187
- /* Deprecated indicator */
188
- text-decoration: line-through;
189
- opacity: 0.7;
190
- }
191
-
192
- .graphql-identifier-overlay.graphql-debug {
193
- /* Debug mode makes overlays visible */
194
- background-color: rgba(59, 130, 246, 0.1) !important;
195
- border: 1px solid rgba(59, 130, 246, 0.3) !important;
196
- }
197
-
198
- /* Kind-specific styles */
199
- .graphql-identifier-overlay.graphql-type {
200
- /* Type identifiers */
201
- }
202
-
203
- .graphql-identifier-overlay.graphql-field {
204
- /* Field identifiers */
205
- }
206
-
207
- .graphql-identifier-overlay.graphql-argument {
208
- /* Argument identifiers */
209
- font-style: italic;
210
- }
211
-
212
- .graphql-identifier-overlay.graphql-variable {
213
- /* Variable identifiers */
214
- color: var(--purple-11);
215
- }
216
-
217
- .graphql-identifier-overlay.graphql-directive {
218
- /* Directive identifiers */
219
- color: var(--amber-11);
220
- }
221
- `
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Minimal styles for GraphQL Document interactive code blocks
3
+ */
4
+
5
+ export const graphqlDocumentStyles = `
6
+ /* Container styles */
7
+ .graphql-document {
8
+ position: relative;
9
+ }
10
+
11
+ .graphql-interaction-layer {
12
+ position: absolute;
13
+ top: 0;
14
+ left: 0;
15
+ right: 0;
16
+ bottom: 0;
17
+ pointer-events: none;
18
+ }
19
+
20
+ .graphql-interaction-layer > * {
21
+ pointer-events: auto;
22
+ }
23
+
24
+ /* Identifier overlay styles */
25
+ .graphql-identifier-overlay {
26
+ transition: background-color 0.2s ease;
27
+ }
28
+
29
+ /* Clickable identifiers get visual feedback */
30
+ .graphql-identifier-overlay.graphql-clickable {
31
+ /* Subtle underline effect using box-shadow to not affect layout */
32
+ box-shadow: 0 1px 0 0 rgba(var(--accent-9), 0.3);
33
+ transition: box-shadow 0.2s ease, background-color 0.2s ease;
34
+ }
35
+
36
+ .graphql-identifier-overlay.graphql-clickable:hover {
37
+ background-color: rgba(var(--accent-3), 0.5);
38
+ box-shadow: 0 1px 0 0 rgba(var(--accent-9), 0.6);
39
+ }
40
+
41
+ /* Active/open state */
42
+ .graphql-identifier-overlay.graphql-tooltip-open {
43
+ background-color: rgba(var(--accent-3), 0.5);
44
+ box-shadow: 0 1px 0 0 var(--accent-9);
45
+ }
46
+
47
+ /* Error state */
48
+ .graphql-identifier-overlay.graphql-error {
49
+ box-shadow: 0 1.5px 0 0 var(--red-9);
50
+ }
51
+
52
+ .graphql-identifier-overlay.graphql-error:hover {
53
+ background-color: rgba(var(--red-3), 0.5);
54
+ }
55
+
56
+ /* Deprecated state */
57
+ .graphql-identifier-overlay.graphql-deprecated {
58
+ text-decoration: line-through;
59
+ opacity: 0.7;
60
+ }
61
+
62
+ /* Debug mode */
63
+ .graphql-identifier-overlay.graphql-debug {
64
+ background-color: rgba(59, 130, 246, 0.1) !important;
65
+ border: 1px solid rgba(59, 130, 246, 0.3) !important;
66
+ }
67
+
68
+ /* Kind-specific colors */
69
+ .graphql-identifier-overlay.graphql-type.graphql-clickable {
70
+ box-shadow: 0 1px 0 0 rgba(var(--blue-9), 0.3);
71
+ }
72
+
73
+ .graphql-identifier-overlay.graphql-field.graphql-clickable {
74
+ box-shadow: 0 1px 0 0 rgba(var(--green-9), 0.3);
75
+ }
76
+
77
+ .graphql-identifier-overlay.graphql-argument.graphql-clickable {
78
+ box-shadow: 0 1px 0 0 rgba(var(--orange-9), 0.3);
79
+ }
80
+
81
+ .graphql-identifier-overlay.graphql-variable {
82
+ box-shadow: 0 1px 0 0 rgba(var(--purple-9), 0.3);
83
+ }
84
+
85
+ .graphql-identifier-overlay.graphql-directive.graphql-clickable {
86
+ box-shadow: 0 1px 0 0 rgba(var(--amber-9), 0.3);
87
+ }
88
+
89
+ /* Popover animation */
90
+ .graphql-identifier-popover {
91
+ animation: graphql-popover-show 150ms ease-out;
92
+ }
93
+
94
+ @keyframes graphql-popover-show {
95
+ from {
96
+ opacity: 0;
97
+ transform: translateY(2px);
98
+ }
99
+ to {
100
+ opacity: 1;
101
+ transform: translateY(0);
102
+ }
103
+ }
104
+
105
+ /* Validation errors */
106
+ .graphql-validation-errors {
107
+ margin-top: 1rem;
108
+ padding: 0.5rem;
109
+ background-color: var(--red-2);
110
+ border: 1px solid var(--red-6);
111
+ border-radius: 4px;
112
+ }
113
+
114
+ .graphql-error {
115
+ color: var(--red-11);
116
+ font-size: 0.875rem;
117
+ margin: 0.25rem 0;
118
+ }
119
+
120
+ /* Loading state */
121
+ .graphql-document.graphql-loading {
122
+ opacity: 0.6;
123
+ pointer-events: none;
124
+ }
125
+
126
+ .graphql-document.graphql-loading::after {
127
+ content: '';
128
+ position: absolute;
129
+ top: 50%;
130
+ left: 50%;
131
+ width: 20px;
132
+ height: 20px;
133
+ margin: -10px 0 0 -10px;
134
+ border: 2px solid var(--gray-6);
135
+ border-top-color: var(--accent-9);
136
+ border-radius: 50%;
137
+ animation: graphql-spinner 0.8s linear infinite;
138
+ }
139
+
140
+ @keyframes graphql-spinner {
141
+ to {
142
+ transform: rotate(360deg);
143
+ }
144
+ }
145
+
146
+ /* Copy button */
147
+ .graphql-document-copy {
148
+ position: absolute;
149
+ top: 1rem;
150
+ right: 1rem;
151
+ opacity: 0;
152
+ transition: opacity 0.2s ease;
153
+ z-index: 10;
154
+ }
155
+
156
+ .graphql-document:hover .graphql-document-copy {
157
+ opacity: 0.8;
158
+ }
159
+
160
+ .graphql-document-copy:hover {
161
+ opacity: 1 !important;
162
+ }
163
+
164
+ .graphql-document-copy[data-copied="true"] {
165
+ opacity: 1 !important;
166
+ color: var(--green-9);
167
+ }`
@@ -1,4 +1,5 @@
1
+ export * from './CopyButton.tsx'
1
2
  export * from './GraphQLDocument.tsx'
2
3
  export * from './GraphQLDocumentWithSchema.tsx'
3
- export * from './HoverTooltip.tsx'
4
+ export * from './GraphQLIdentifierPopover.tsx'
4
5
  export * from './IdentifierLink.tsx'
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Unit tests for tooltip state management hook
3
+ *
4
+ * @vitest-environment jsdom
5
+ */
6
+
7
+ import { act, renderHook } from '@testing-library/react'
8
+ import { describe, expect, it, vi } from 'vitest'
9
+ import { useTooltipState } from './use-tooltip-state.ts'
10
+
11
+ describe('useTooltipState', () => {
12
+ it('shows tooltip after hover delay', () => {
13
+ vi.useFakeTimers()
14
+ const { result } = renderHook(() => useTooltipState({ showDelay: 300 }))
15
+
16
+ act(() => {
17
+ result.current.onHoverStart('field-1')
18
+ })
19
+ expect(result.current.isOpen('field-1')).toBe(false)
20
+
21
+ act(() => {
22
+ vi.advanceTimersByTime(300)
23
+ })
24
+ expect(result.current.isOpen('field-1')).toBe(true)
25
+ vi.useRealTimers()
26
+ })
27
+
28
+ it('hides tooltip after hover end delay', () => {
29
+ vi.useFakeTimers()
30
+ const { result } = renderHook(() => useTooltipState())
31
+
32
+ // Show tooltip
33
+ act(() => {
34
+ result.current.onHoverStart('field-1')
35
+ vi.advanceTimersByTime(300)
36
+ })
37
+
38
+ // Trigger hide
39
+ act(() => {
40
+ result.current.onHoverEnd('field-1')
41
+ })
42
+ expect(result.current.isOpen('field-1')).toBe(true) // Still open during delay
43
+
44
+ act(() => {
45
+ vi.advanceTimersByTime(200)
46
+ })
47
+ expect(result.current.isOpen('field-1')).toBe(false)
48
+ vi.useRealTimers()
49
+ })
50
+
51
+ it('pins and unpins tooltip on toggle', () => {
52
+ const { result } = renderHook(() => useTooltipState())
53
+
54
+ act(() => {
55
+ result.current.onTogglePin('field-1')
56
+ })
57
+ expect(result.current.isPinned('field-1')).toBe(true)
58
+
59
+ act(() => {
60
+ result.current.onTogglePin('field-1')
61
+ })
62
+ expect(result.current.isPinned('field-1')).toBe(false)
63
+ })
64
+
65
+ it('allows multiple pins when enabled', () => {
66
+ const { result } = renderHook(() => useTooltipState())
67
+
68
+ act(() => {
69
+ result.current.onTogglePin('field-1')
70
+ result.current.onTogglePin('field-2')
71
+ })
72
+
73
+ expect(result.current.isPinned('field-1')).toBe(true)
74
+ expect(result.current.isPinned('field-2')).toBe(true)
75
+ })
76
+ })