polen 0.10.0-next.12 → 0.10.0-next.14

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 (141) hide show
  1. package/build/api/vite/plugins/build.d.ts.map +1 -1
  2. package/build/api/vite/plugins/build.js +11 -3
  3. package/build/api/vite/plugins/build.js.map +1 -1
  4. package/build/api/vite/plugins/core.d.ts.map +1 -1
  5. package/build/api/vite/plugins/core.js +12 -10
  6. package/build/api/vite/plugins/core.js.map +1 -1
  7. package/build/api/vite/plugins/pages.d.ts.map +1 -1
  8. package/build/api/vite/plugins/pages.js +6 -7
  9. package/build/api/vite/plugins/pages.js.map +1 -1
  10. package/build/api/vite/plugins/serve.d.ts.map +1 -1
  11. package/build/api/vite/plugins/serve.js +47 -7
  12. package/build/api/vite/plugins/serve.js.map +1 -1
  13. package/build/lib/file-router/diagnostic-reporter.js +2 -2
  14. package/build/lib/file-router/diagnostic-reporter.js.map +1 -1
  15. package/build/lib/graphql-document/components/CopyButton.d.ts +19 -0
  16. package/build/lib/graphql-document/components/CopyButton.d.ts.map +1 -0
  17. package/build/lib/graphql-document/components/CopyButton.js +43 -0
  18. package/build/lib/graphql-document/components/CopyButton.js.map +1 -0
  19. package/build/lib/graphql-document/components/GraphQLDocument.d.ts +0 -4
  20. package/build/lib/graphql-document/components/GraphQLDocument.d.ts.map +1 -1
  21. package/build/lib/graphql-document/components/GraphQLDocument.js +52 -83
  22. package/build/lib/graphql-document/components/GraphQLDocument.js.map +1 -1
  23. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.d.ts +33 -0
  24. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.d.ts.map +1 -0
  25. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.js +48 -0
  26. package/build/lib/graphql-document/components/GraphQLIdentifierPopover.js.map +1 -0
  27. package/build/lib/graphql-document/components/IdentifierLink.d.ts +15 -13
  28. package/build/lib/graphql-document/components/IdentifierLink.d.ts.map +1 -1
  29. package/build/lib/graphql-document/components/IdentifierLink.js +51 -117
  30. package/build/lib/graphql-document/components/IdentifierLink.js.map +1 -1
  31. package/build/lib/graphql-document/components/graphql-document-styles.d.ts +5 -0
  32. package/build/lib/graphql-document/components/graphql-document-styles.d.ts.map +1 -0
  33. package/build/lib/graphql-document/components/graphql-document-styles.js +167 -0
  34. package/build/lib/graphql-document/components/graphql-document-styles.js.map +1 -0
  35. package/build/lib/graphql-document/components/index.d.ts +2 -1
  36. package/build/lib/graphql-document/components/index.d.ts.map +1 -1
  37. package/build/lib/graphql-document/components/index.js +2 -1
  38. package/build/lib/graphql-document/components/index.js.map +1 -1
  39. package/build/lib/graphql-document/hooks/use-tooltip-state.d.ts +43 -0
  40. package/build/lib/graphql-document/hooks/use-tooltip-state.d.ts.map +1 -0
  41. package/build/lib/graphql-document/hooks/use-tooltip-state.js +132 -0
  42. package/build/lib/graphql-document/hooks/use-tooltip-state.js.map +1 -0
  43. package/build/lib/graphql-document/positioning-simple.d.ts +0 -5
  44. package/build/lib/graphql-document/positioning-simple.d.ts.map +1 -1
  45. package/build/lib/graphql-document/positioning-simple.js +78 -90
  46. package/build/lib/graphql-document/positioning-simple.js.map +1 -1
  47. package/build/lib/kit-temp.d.ts +103 -0
  48. package/build/lib/kit-temp.d.ts.map +1 -1
  49. package/build/lib/kit-temp.js +236 -2
  50. package/build/lib/kit-temp.js.map +1 -1
  51. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.d.ts +1 -8
  52. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.d.ts.map +1 -1
  53. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.js +48 -53
  54. package/build/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.js.map +1 -1
  55. package/build/package-paths.js +3 -3
  56. package/build/package-paths.js.map +1 -1
  57. package/build/template/components/Link.d.ts +1 -1
  58. package/build/template/components/Link.d.ts.map +1 -1
  59. package/build/template/components/Link.js +14 -5
  60. package/build/template/components/Link.js.map +1 -1
  61. package/build/template/components/content/GraphQLDocumentWithSchema.d.ts.map +1 -1
  62. package/build/template/components/content/GraphQLDocumentWithSchema.js +0 -3
  63. package/build/template/components/content/GraphQLDocumentWithSchema.js.map +1 -1
  64. package/build/template/components/content/GraphQLDocumentWrapper.d.ts.map +1 -1
  65. package/build/template/components/content/GraphQLDocumentWrapper.js +8 -7
  66. package/build/template/components/content/GraphQLDocumentWrapper.js.map +1 -1
  67. package/build/template/components/sidebar/SidebarItem.js +2 -2
  68. package/build/template/entry.client.d.ts.map +1 -1
  69. package/build/template/entry.client.js +0 -3
  70. package/build/template/entry.client.js.map +1 -1
  71. package/build/template/hooks/useClientOnly.d.ts +9 -0
  72. package/build/template/hooks/useClientOnly.d.ts.map +1 -0
  73. package/build/template/hooks/useClientOnly.js +16 -0
  74. package/build/template/hooks/useClientOnly.js.map +1 -0
  75. package/build/template/routes/root.d.ts.map +1 -1
  76. package/build/template/routes/root.js +2 -150
  77. package/build/template/routes/root.js.map +1 -1
  78. package/build/template/server/app.d.ts +8 -1
  79. package/build/template/server/app.d.ts.map +1 -1
  80. package/build/template/server/app.js +21 -21
  81. package/build/template/server/app.js.map +1 -1
  82. package/build/template/server/create-page-html-response.d.ts +7 -0
  83. package/build/template/server/create-page-html-response.d.ts.map +1 -0
  84. package/build/template/server/{render-page.js → create-page-html-response.js} +11 -16
  85. package/build/template/server/create-page-html-response.js.map +1 -0
  86. package/build/template/server/main.js +2 -1
  87. package/build/template/server/main.js.map +1 -1
  88. package/build/template/server/middleware/page.d.ts +4 -0
  89. package/build/template/server/middleware/page.d.ts.map +1 -0
  90. package/build/template/server/middleware/page.js +15 -0
  91. package/build/template/server/middleware/page.js.map +1 -0
  92. package/build/template/server/middleware/unsupported-assets.d.ts +10 -0
  93. package/build/template/server/middleware/unsupported-assets.d.ts.map +1 -0
  94. package/build/template/server/middleware/unsupported-assets.js +21 -0
  95. package/build/template/server/middleware/unsupported-assets.js.map +1 -0
  96. package/build/template/server/ssg/generate.d.ts.map +1 -1
  97. package/build/template/server/ssg/generate.js +33 -34
  98. package/build/template/server/ssg/generate.js.map +1 -1
  99. package/build/template/styles/code-block.css +218 -0
  100. package/package.json +4 -2
  101. package/src/api/singletons/markdown/markdown.test.ts +1 -1
  102. package/src/api/vite/plugins/build.ts +97 -89
  103. package/src/api/vite/plugins/core.ts +15 -10
  104. package/src/api/vite/plugins/pages.ts +9 -7
  105. package/src/api/vite/plugins/serve.ts +62 -9
  106. package/src/lib/file-router/diagnostic-reporter.ts +2 -2
  107. package/src/lib/graphql-document/components/CopyButton.tsx +76 -0
  108. package/src/lib/graphql-document/components/GraphQLDocument.tsx +73 -95
  109. package/src/lib/graphql-document/components/GraphQLIdentifierPopover.tsx +197 -0
  110. package/src/lib/graphql-document/components/IdentifierLink.tsx +105 -166
  111. package/src/lib/graphql-document/components/graphql-document-styles.ts +167 -0
  112. package/src/lib/graphql-document/components/index.ts +2 -1
  113. package/src/lib/graphql-document/hooks/use-tooltip-state.test.ts +76 -0
  114. package/src/lib/graphql-document/hooks/use-tooltip-state.ts +191 -0
  115. package/src/lib/graphql-document/positioning-simple.test.ts +18 -22
  116. package/src/lib/graphql-document/positioning-simple.ts +97 -108
  117. package/src/lib/kit-temp.test.ts +15 -3
  118. package/src/lib/kit-temp.ts +304 -4
  119. package/src/lib/vite-plugin-reactive-data/vite-plugin-reactive-data.ts +52 -58
  120. package/src/package-paths.ts +3 -3
  121. package/src/template/components/Link.tsx +20 -12
  122. package/src/template/components/content/GraphQLDocumentWithSchema.tsx +0 -5
  123. package/src/template/components/content/GraphQLDocumentWrapper.tsx +14 -7
  124. package/src/template/components/sidebar/SidebarItem.tsx +2 -2
  125. package/src/template/entry.client.tsx +0 -3
  126. package/src/template/hooks/useClientOnly.ts +21 -0
  127. package/src/template/routes/root.tsx +0 -159
  128. package/src/template/server/app.ts +33 -23
  129. package/src/template/server/{render-page.tsx → create-page-html-response.ts} +19 -16
  130. package/src/template/server/main.ts +2 -1
  131. package/src/template/server/middleware/page.ts +19 -0
  132. package/src/template/server/middleware/unsupported-assets.ts +25 -0
  133. package/src/template/server/ssg/generate.ts +68 -72
  134. package/build/lib/graphql-document/components/HoverTooltip.d.ts +0 -35
  135. package/build/lib/graphql-document/components/HoverTooltip.d.ts.map +0 -1
  136. package/build/lib/graphql-document/components/HoverTooltip.js +0 -132
  137. package/build/lib/graphql-document/components/HoverTooltip.js.map +0 -1
  138. package/build/template/server/render-page.d.ts +0 -3
  139. package/build/template/server/render-page.d.ts.map +0 -1
  140. package/build/template/server/render-page.js.map +0 -1
  141. package/src/lib/graphql-document/components/HoverTooltip.tsx +0 -282
@@ -0,0 +1,191 @@
1
+ /**
2
+ * State management for GraphQL document tooltips
3
+ *
4
+ * Handles hover delays, pinning, and multiple tooltip coordination
5
+ */
6
+
7
+ import { React } from '#dep/react/index'
8
+
9
+ export interface TooltipState {
10
+ /** Currently visible tooltip (via hover) */
11
+ hoveredId: string | null
12
+ /** Set of pinned tooltip IDs */
13
+ pinnedIds: Set<string>
14
+ /** ID pending show (waiting for delay) */
15
+ pendingShowId: string | null
16
+ /** ID pending hide (waiting for grace period) */
17
+ pendingHideId: string | null
18
+ }
19
+
20
+ export interface UseTooltipStateOptions {
21
+ /** Delay before showing tooltip on hover (ms) */
22
+ showDelay?: number
23
+ /** Delay before hiding tooltip on mouse leave (ms) */
24
+ hideDelay?: number
25
+ /** Whether to allow multiple pinned tooltips */
26
+ allowMultiplePins?: boolean
27
+ }
28
+
29
+ export interface UseTooltipStateReturn {
30
+ /** Check if a tooltip should be visible */
31
+ isOpen: (id: string) => boolean
32
+ /** Check if a tooltip is pinned */
33
+ isPinned: (id: string) => boolean
34
+ /** Handle hover start */
35
+ onHoverStart: (id: string) => void
36
+ /** Handle hover end */
37
+ onHoverEnd: (id: string) => void
38
+ /** Handle click (toggle pin) */
39
+ onTogglePin: (id: string) => void
40
+ /** Handle tooltip content hover (cancels hide) */
41
+ onTooltipHover: (id: string) => void
42
+ /** Unpin a specific tooltip */
43
+ unpin: (id: string) => void
44
+ /** Unpin all tooltips */
45
+ unpinAll: () => void
46
+ }
47
+
48
+ export const useTooltipState = (options: UseTooltipStateOptions = {}): UseTooltipStateReturn => {
49
+ const {
50
+ showDelay = 300,
51
+ hideDelay = 200,
52
+ allowMultiplePins = true,
53
+ } = options
54
+
55
+ const [hoveredId, setHoveredId] = React.useState<string | null>(null)
56
+ const [pinnedIds, setPinnedIds] = React.useState<Set<string>>(new Set())
57
+ const [pendingShowId, setPendingShowId] = React.useState<string | null>(null)
58
+ const [pendingHideId, setPendingHideId] = React.useState<string | null>(null)
59
+
60
+ // Timer refs
61
+ const showTimerRef = React.useRef<NodeJS.Timeout | null>(null)
62
+ const hideTimerRef = React.useRef<NodeJS.Timeout | null>(null)
63
+
64
+ // Clear any pending timers
65
+ const clearTimers = React.useCallback(() => {
66
+ if (showTimerRef.current) {
67
+ clearTimeout(showTimerRef.current)
68
+ showTimerRef.current = null
69
+ }
70
+ if (hideTimerRef.current) {
71
+ clearTimeout(hideTimerRef.current)
72
+ hideTimerRef.current = null
73
+ }
74
+ setPendingShowId(null)
75
+ setPendingHideId(null)
76
+ }, [])
77
+
78
+ // Check if tooltip should be visible
79
+ const isOpen = React.useCallback((id: string): boolean => {
80
+ return hoveredId === id || pinnedIds.has(id)
81
+ }, [hoveredId, pinnedIds])
82
+
83
+ // Check if tooltip is pinned
84
+ const isPinned = React.useCallback((id: string): boolean => {
85
+ return pinnedIds.has(id)
86
+ }, [pinnedIds])
87
+
88
+ // Handle hover start
89
+ const onHoverStart = React.useCallback((id: string) => {
90
+ // Don't show if already pinned
91
+ if (pinnedIds.has(id)) return
92
+
93
+ // Cancel any pending hide for this ID
94
+ if (pendingHideId === id) {
95
+ clearTimeout(hideTimerRef.current!)
96
+ hideTimerRef.current = null
97
+ setPendingHideId(null)
98
+ return
99
+ }
100
+
101
+ // Clear any other pending operations
102
+ clearTimers()
103
+
104
+ // Schedule show
105
+ setPendingShowId(id)
106
+ showTimerRef.current = setTimeout(() => {
107
+ setHoveredId(id)
108
+ setPendingShowId(null)
109
+ }, showDelay)
110
+ }, [pinnedIds, pendingHideId, clearTimers, showDelay])
111
+
112
+ // Handle hover end
113
+ const onHoverEnd = React.useCallback((id: string) => {
114
+ // Don't hide if pinned
115
+ if (pinnedIds.has(id)) return
116
+
117
+ // Cancel pending show if still waiting
118
+ if (pendingShowId === id) {
119
+ clearTimeout(showTimerRef.current!)
120
+ showTimerRef.current = null
121
+ setPendingShowId(null)
122
+ return
123
+ }
124
+
125
+ // Only hide if currently showing this tooltip
126
+ if (hoveredId === id) {
127
+ setPendingHideId(id)
128
+ hideTimerRef.current = setTimeout(() => {
129
+ // First set hovered to null to trigger close animation
130
+ setHoveredId(null)
131
+ setPendingHideId(null)
132
+ }, hideDelay)
133
+ }
134
+ }, [pinnedIds, pendingShowId, hoveredId, hideDelay])
135
+
136
+ // Handle tooltip content hover (cancels hide)
137
+ const onTooltipHover = React.useCallback((id: string) => {
138
+ if (pendingHideId === id) {
139
+ clearTimeout(hideTimerRef.current!)
140
+ hideTimerRef.current = null
141
+ setPendingHideId(null)
142
+ }
143
+ }, [pendingHideId])
144
+
145
+ // Toggle pin state
146
+ const onTogglePin = React.useCallback((id: string) => {
147
+ clearTimers()
148
+
149
+ setPinnedIds((prev: Set<string>) => {
150
+ const next = new Set(prev)
151
+ if (next.has(id)) {
152
+ // Unpin
153
+ next.delete(id)
154
+ setHoveredId(null) // Also clear hover state
155
+ } else {
156
+ // Pin
157
+ if (!allowMultiplePins) {
158
+ next.clear() // Clear other pins
159
+ }
160
+ next.add(id)
161
+ setHoveredId(null) // Clear hover state since it's now pinned
162
+ }
163
+ return next
164
+ })
165
+ }, [clearTimers, allowMultiplePins])
166
+
167
+ // Unpin specific tooltip
168
+ const unpin = React.useCallback((id: string) => {
169
+ setPinnedIds((prev: Set<string>) => {
170
+ const next = new Set(prev)
171
+ next.delete(id)
172
+ return next
173
+ })
174
+ }, [])
175
+
176
+ // Unpin all tooltips
177
+ const unpinAll = React.useCallback(() => {
178
+ setPinnedIds(new Set())
179
+ }, [])
180
+
181
+ return {
182
+ isOpen,
183
+ isPinned,
184
+ onHoverStart,
185
+ onHoverEnd,
186
+ onTogglePin,
187
+ onTooltipHover,
188
+ unpin,
189
+ unpinAll,
190
+ }
191
+ }
@@ -33,13 +33,11 @@ describe('Simple Positioning Engine', () => {
33
33
  const container = document.createElement('div')
34
34
  container.innerHTML = `
35
35
  <pre class="shiki">
36
- <code>
37
- <span class="line">query GetUser {</span>
38
- <span class="line"> user {</span>
39
- <span class="line"> name</span>
40
- <span class="line"> }</span>
41
- <span class="line">}</span>
42
- </code>
36
+ <code>query GetUser {
37
+ user {
38
+ name
39
+ }
40
+ }</code>
43
41
  </pre>
44
42
  `
45
43
 
@@ -49,7 +47,8 @@ describe('Simple Positioning Engine', () => {
49
47
  createTestIdentifier('name', 3, 5, 'Field'),
50
48
  ]
51
49
 
52
- calculator.prepareCodeBlock(container, identifiers)
50
+ const codeElement = container.querySelector('code')!
51
+ calculator.prepareCodeBlock(codeElement, identifiers)
53
52
 
54
53
  // Check that identifiers were wrapped
55
54
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
@@ -66,9 +65,7 @@ describe('Simple Positioning Engine', () => {
66
65
  const container = document.createElement('div')
67
66
  container.innerHTML = `
68
67
  <pre class="shiki">
69
- <code>
70
- <span class="line">query GetUserById($id: ID!) {</span>
71
- </code>
68
+ <code>query GetUserById($id: ID!) {</code>
72
69
  </pre>
73
70
  `
74
71
 
@@ -79,7 +76,8 @@ describe('Simple Positioning Engine', () => {
79
76
  createTestIdentifier('ID', 1, 24, 'Type'),
80
77
  ]
81
78
 
82
- calculator.prepareCodeBlock(container, identifiers)
79
+ const codeElement = container.querySelector('code')!
80
+ calculator.prepareCodeBlock(codeElement, identifiers)
83
81
 
84
82
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
85
83
  expect(wrappedElements.length).toBe(4)
@@ -140,9 +138,7 @@ describe('Simple Positioning Engine', () => {
140
138
  const container = document.createElement('div')
141
139
  container.innerHTML = `
142
140
  <pre class="shiki">
143
- <code>
144
- <span class="line"><span data-graphql-id="existing">user</span> {</span>
145
- </code>
141
+ <code><span data-graphql-id="existing">user</span> {</code>
146
142
  </pre>
147
143
  `
148
144
 
@@ -150,7 +146,8 @@ describe('Simple Positioning Engine', () => {
150
146
  createTestIdentifier('user', 1, 1),
151
147
  ]
152
148
 
153
- calculator.prepareCodeBlock(container, identifiers)
149
+ const codeElement = container.querySelector('code')!
150
+ calculator.prepareCodeBlock(codeElement, identifiers)
154
151
 
155
152
  // Should still only have one wrapped element
156
153
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
@@ -162,11 +159,9 @@ describe('Simple Positioning Engine', () => {
162
159
  const container = document.createElement('div')
163
160
  container.innerHTML = `
164
161
  <pre class="shiki">
165
- <code>
166
- <span class="line">query {</span>
167
- <span class="line"></span>
168
- <span class="line"> user</span>
169
- </code>
162
+ <code>query {
163
+
164
+ user</code>
170
165
  </pre>
171
166
  `
172
167
 
@@ -175,8 +170,9 @@ describe('Simple Positioning Engine', () => {
175
170
  createTestIdentifier('user', 3, 3),
176
171
  ]
177
172
 
173
+ const codeElement = container.querySelector('code')!
178
174
  expect(() => {
179
- calculator.prepareCodeBlock(container, identifiers)
175
+ calculator.prepareCodeBlock(codeElement, identifiers)
180
176
  }).not.toThrow()
181
177
 
182
178
  const wrappedElements = container.querySelectorAll('[data-graphql-id]')
@@ -50,21 +50,104 @@ export class SimplePositionCalculator {
50
50
  containerElement: Element,
51
51
  identifiers: Identifier[],
52
52
  ): void {
53
- // Sort all identifiers by position (right to left, bottom to top)
54
- const sortedIdentifiers = [...identifiers].sort((a, b) => {
55
- // Sort by line first (bottom to top)
56
- if (a.position.line !== b.position.line) {
57
- return b.position.line - a.position.line
58
- }
59
- // Then by column (right to left)
60
- return b.position.column - a.position.column
61
- })
53
+ // Get the full text content of the container
54
+ const fullText = containerElement.textContent || ''
55
+ const lines = fullText.split('\n')
56
+
57
+ // Build a map of line start positions in the full text
58
+ const lineStartPositions: number[] = [0]
59
+ for (let i = 0; i < lines.length - 1; i++) {
60
+ const lineLength = lines[i]?.length ?? 0
61
+ lineStartPositions.push(lineStartPositions[i]! + lineLength + 1) // +1 for newline
62
+ }
63
+
64
+ // Process identifiers by line
65
+ for (const identifier of identifiers) {
66
+ const lineIndex = identifier.position.line - 1
67
+ if (lineIndex >= lines.length || lineIndex < 0) continue
68
+
69
+ const lineText = lines[lineIndex]
70
+ if (!lineText) continue
71
+
72
+ const columnIndex = identifier.position.column - 1
73
+
74
+ // Check if the identifier exists at the expected position
75
+ if (lineText.substring(columnIndex).startsWith(identifier.name)) {
76
+ // Calculate the absolute position in the full text
77
+ const lineStartPosition = lineStartPositions[lineIndex] ?? 0
78
+ const absolutePosition = lineStartPosition + columnIndex
79
+
80
+ // Check if already wrapped at this specific position
81
+ const existingWrapped = containerElement.querySelectorAll(`[data-graphql-id]`)
82
+ let alreadyWrapped = false
83
+ for (const wrapped of existingWrapped) {
84
+ if (wrapped.textContent === identifier.name) {
85
+ const startPos = parseInt(wrapped.getAttribute('data-graphql-start') || '0')
86
+ if (startPos === identifier.position.start) {
87
+ alreadyWrapped = true
88
+ break
89
+ }
90
+ }
91
+ }
92
+ if (alreadyWrapped) continue
93
+
94
+ // Create wrapper span
95
+ const wrapper = document.createElement('span')
96
+ const id = `${identifier.position.start}-${identifier.name}-${identifier.kind}`
97
+ wrapper.setAttribute('data-graphql-id', id)
98
+ wrapper.setAttribute('data-graphql-name', identifier.name)
99
+ wrapper.setAttribute('data-graphql-kind', identifier.kind)
100
+ wrapper.setAttribute('data-graphql-start', String(identifier.position.start))
101
+ wrapper.setAttribute('data-graphql-end', String(identifier.position.end))
102
+ wrapper.setAttribute('data-graphql-line', String(identifier.position.line))
103
+ wrapper.setAttribute('data-graphql-column', String(identifier.position.column))
104
+ wrapper.setAttribute('data-graphql-path', identifier.schemaPath.join(','))
105
+
106
+ // Find the position in the container and wrap the text
107
+ const walker = document.createTreeWalker(
108
+ containerElement,
109
+ NodeFilter.SHOW_TEXT,
110
+ null,
111
+ )
112
+
113
+ let currentPos = 0
114
+ let node: Node | null
115
+
116
+ while (node = walker.nextNode()) {
117
+ const textNode = node as Text
118
+ const text = textNode.textContent || ''
119
+
120
+ // Check if this text node contains our identifier
121
+ if (currentPos <= absolutePosition && absolutePosition < currentPos + text.length) {
122
+ const relativePos = absolutePosition - currentPos
123
+
124
+ if (text.substring(relativePos).startsWith(identifier.name)) {
125
+ // Split the text node
126
+ const before = text.substring(0, relativePos)
127
+ const identifierText = identifier.name
128
+ const after = text.substring(relativePos + identifierText.length)
129
+
130
+ const parent = textNode.parentNode!
131
+
132
+ if (before) {
133
+ parent.insertBefore(document.createTextNode(before), textNode)
134
+ }
135
+
136
+ wrapper.textContent = identifierText
137
+ parent.insertBefore(wrapper, textNode)
138
+
139
+ if (after) {
140
+ parent.insertBefore(document.createTextNode(after), textNode)
141
+ }
142
+
143
+ parent.removeChild(textNode)
144
+ break
145
+ }
146
+ }
62
147
 
63
- // Process all identifiers
64
- let wrappedCount = 0
65
- for (const identifier of sortedIdentifiers) {
66
- const wrapped = this.wrapIdentifier(containerElement, identifier)
67
- if (wrapped) wrappedCount++
148
+ currentPos += text.length
149
+ }
150
+ }
68
151
  }
69
152
  }
70
153
 
@@ -117,100 +200,6 @@ export class SimplePositionCalculator {
117
200
 
118
201
  return results
119
202
  }
120
-
121
- /**
122
- * Wrap an identifier in a span for positioning
123
- * Returns true if the identifier was successfully wrapped
124
- */
125
- private wrapIdentifier(containerElement: Element, identifier: Identifier): boolean {
126
- const walker = document.createTreeWalker(
127
- containerElement,
128
- NodeFilter.SHOW_TEXT,
129
- null,
130
- )
131
-
132
- let currentLine = 1
133
- let currentColumn = 1
134
- let node: Node | null
135
-
136
- while (node = walker.nextNode()) {
137
- const textNode = node as Text
138
- const text = textNode.textContent || ''
139
-
140
- // Check if already wrapped
141
- if (textNode.parentElement?.hasAttribute('data-graphql-id')) {
142
- // Update position tracking and continue
143
- for (const char of text) {
144
- if (char === '\n') {
145
- currentLine++
146
- currentColumn = 1
147
- } else {
148
- currentColumn++
149
- }
150
- }
151
- continue
152
- }
153
-
154
- // Track position in the text
155
- let textIndex = 0
156
- while (textIndex < text.length) {
157
- // Check if we're at the identifier's position
158
- if (
159
- currentLine === identifier.position.line
160
- && currentColumn === identifier.position.column
161
- ) {
162
- // Verify it's actually our identifier
163
- const remainingText = text.substring(textIndex)
164
- if (remainingText.startsWith(identifier.name)) {
165
- // Create a unique ID for this identifier
166
- const id = `${identifier.position.start}-${identifier.name}-${identifier.kind}`
167
-
168
- // Split the text node and wrap the identifier
169
- const before = text.substring(0, textIndex)
170
- const after = text.substring(textIndex + identifier.name.length)
171
-
172
- const span = document.createElement('span')
173
- span.setAttribute('data-graphql-id', id)
174
- span.setAttribute('data-graphql-name', identifier.name)
175
- span.setAttribute('data-graphql-kind', identifier.kind)
176
- span.setAttribute('data-graphql-start', String(identifier.position.start))
177
- span.setAttribute('data-graphql-end', String(identifier.position.end))
178
- span.setAttribute('data-graphql-line', String(identifier.position.line))
179
- span.setAttribute('data-graphql-column', String(identifier.position.column))
180
- span.setAttribute('data-graphql-path', identifier.schemaPath.join(','))
181
- span.textContent = identifier.name
182
-
183
- const parent = textNode.parentNode!
184
-
185
- if (before) {
186
- parent.insertBefore(document.createTextNode(before), textNode)
187
- }
188
-
189
- parent.insertBefore(span, textNode)
190
-
191
- if (after) {
192
- parent.insertBefore(document.createTextNode(after), textNode)
193
- }
194
-
195
- parent.removeChild(textNode)
196
- return true
197
- }
198
- }
199
-
200
- // Update position tracking
201
- const char = text[textIndex]
202
- if (char === '\n') {
203
- currentLine++
204
- currentColumn = 1
205
- } else {
206
- currentColumn++
207
- }
208
- textIndex++
209
- }
210
- }
211
-
212
- return false // Identifier not found
213
- }
214
203
  }
215
204
 
216
205
  /**
@@ -74,8 +74,13 @@ describe('property-based tests', () => {
74
74
  expect(inAllowed).toBe(!inDenied)
75
75
  })
76
76
 
77
- // Combined they reconstruct the original object
78
- expect({ ...allowed, ...denied }).toEqual(obj)
77
+ // Combined they reconstruct the original object (only own properties)
78
+ const reconstructed = { ...allowed, ...denied }
79
+ const ownPropsObj = Object.keys(obj).reduce((acc, key) => {
80
+ acc[key] = obj[key]
81
+ return acc
82
+ }, {} as any)
83
+ expect(reconstructed).toEqual(ownPropsObj)
79
84
  },
80
85
  ),
81
86
  )
@@ -87,7 +92,14 @@ describe('property-based tests', () => {
87
92
  fc.object(),
88
93
  (obj) => {
89
94
  const filtered = objFilter(obj, () => true)
90
- expect(filtered).toEqual(obj)
95
+
96
+ // Object.keys doesn't include __proto__, so we need to handle it specially
97
+ const objWithoutProto = Object.keys(obj).reduce((acc, key) => {
98
+ acc[key] = obj[key]
99
+ return acc
100
+ }, {} as any)
101
+
102
+ expect(filtered).toEqual(objWithoutProto)
91
103
 
92
104
  // Values are the same reference
93
105
  Object.keys(filtered).forEach(key => {