@antigenic-oss/paint 0.1.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.
Files changed (158) hide show
  1. package/LICENSE +178 -0
  2. package/NOTICE +4 -0
  3. package/README.md +180 -0
  4. package/bin/paint.js +266 -0
  5. package/next-env.d.ts +6 -0
  6. package/next.config.ts +19 -0
  7. package/package.json +81 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/dev-editor-inspector.js +1872 -0
  10. package/src/app/api/claude/analyze/route.ts +319 -0
  11. package/src/app/api/claude/apply/route.ts +185 -0
  12. package/src/app/api/claude/pick-folder/route.ts +64 -0
  13. package/src/app/api/claude/scan/route.ts +221 -0
  14. package/src/app/api/claude/status/route.ts +55 -0
  15. package/src/app/api/project/scan/route.ts +634 -0
  16. package/src/app/api/project-scan/css-variables/route.ts +238 -0
  17. package/src/app/api/project-scan/route.ts +40 -0
  18. package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
  19. package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
  20. package/src/app/docs/DocsClient.tsx +322 -0
  21. package/src/app/docs/layout.tsx +7 -0
  22. package/src/app/docs/page.tsx +855 -0
  23. package/src/app/globals.css +176 -0
  24. package/src/app/layout.tsx +19 -0
  25. package/src/app/page.tsx +46 -0
  26. package/src/bridge/api-handlers.ts +885 -0
  27. package/src/bridge/proxy-handler.ts +329 -0
  28. package/src/bridge/server.ts +113 -0
  29. package/src/components/BreakpointTabs.tsx +72 -0
  30. package/src/components/ChangeSummaryModal.tsx +267 -0
  31. package/src/components/ConnectModal.tsx +994 -0
  32. package/src/components/Editor.tsx +90 -0
  33. package/src/components/PageSelector.tsx +208 -0
  34. package/src/components/PreviewFrame.tsx +299 -0
  35. package/src/components/ProjectFolderBanner.tsx +91 -0
  36. package/src/components/ResponsiveToolbar.tsx +222 -0
  37. package/src/components/TargetSelector.tsx +243 -0
  38. package/src/components/TopBar.tsx +315 -0
  39. package/src/components/common/CollapsibleSection.tsx +36 -0
  40. package/src/components/common/ColorPicker.tsx +920 -0
  41. package/src/components/common/EditablePre.tsx +136 -0
  42. package/src/components/common/ErrorBoundary.tsx +65 -0
  43. package/src/components/common/ResizablePanel.tsx +83 -0
  44. package/src/components/common/ScanAnimation.tsx +76 -0
  45. package/src/components/common/ToastContainer.tsx +97 -0
  46. package/src/components/common/UnitInput.tsx +77 -0
  47. package/src/components/common/VariableColorPicker.tsx +622 -0
  48. package/src/components/left-panel/AddElementPanel.tsx +237 -0
  49. package/src/components/left-panel/ComponentsPanel.tsx +609 -0
  50. package/src/components/left-panel/IconSidebar.tsx +99 -0
  51. package/src/components/left-panel/LayerNode.tsx +874 -0
  52. package/src/components/left-panel/LayerSearch.tsx +23 -0
  53. package/src/components/left-panel/LayersPanel.tsx +52 -0
  54. package/src/components/left-panel/LeftPanel.tsx +122 -0
  55. package/src/components/left-panel/PagesPanel.tsx +114 -0
  56. package/src/components/left-panel/icons.tsx +162 -0
  57. package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
  58. package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
  59. package/src/components/right-panel/ElementLogBox.tsx +248 -0
  60. package/src/components/right-panel/PanelTabs.tsx +83 -0
  61. package/src/components/right-panel/RightPanel.tsx +41 -0
  62. package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
  63. package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
  64. package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
  65. package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
  66. package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
  67. package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
  68. package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
  69. package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
  70. package/src/components/right-panel/claude/DiffCard.tsx +130 -0
  71. package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
  72. package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
  73. package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
  74. package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
  75. package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
  76. package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
  77. package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
  78. package/src/components/right-panel/design/BorderSection.tsx +161 -0
  79. package/src/components/right-panel/design/CSSRawView.tsx +412 -0
  80. package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
  81. package/src/components/right-panel/design/DesignPanel.tsx +275 -0
  82. package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
  83. package/src/components/right-panel/design/GradientEditor.tsx +726 -0
  84. package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
  85. package/src/components/right-panel/design/PositionSection.tsx +865 -0
  86. package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
  87. package/src/components/right-panel/design/SVGSection.tsx +361 -0
  88. package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
  89. package/src/components/right-panel/design/SizeSection.tsx +183 -0
  90. package/src/components/right-panel/design/TextSection.tsx +719 -0
  91. package/src/components/right-panel/design/icons.tsx +948 -0
  92. package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
  93. package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
  94. package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
  95. package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
  96. package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
  97. package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
  98. package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
  99. package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
  100. package/src/hooks/useBridge.ts +95 -0
  101. package/src/hooks/useChangeTracker.ts +563 -0
  102. package/src/hooks/useClaudeAPI.ts +118 -0
  103. package/src/hooks/useDOMTree.ts +25 -0
  104. package/src/hooks/useKeyboardShortcuts.ts +76 -0
  105. package/src/hooks/usePostMessage.ts +589 -0
  106. package/src/hooks/useProjectScan.ts +204 -0
  107. package/src/hooks/useResizable.ts +20 -0
  108. package/src/hooks/useSelectedElement.ts +51 -0
  109. package/src/hooks/useTargetUrl.ts +81 -0
  110. package/src/inspector/DOMTraverser.ts +71 -0
  111. package/src/inspector/ElementSelector.ts +23 -0
  112. package/src/inspector/HoverHighlighter.ts +54 -0
  113. package/src/inspector/SelectionHighlighter.ts +27 -0
  114. package/src/inspector/StyleExtractor.ts +19 -0
  115. package/src/inspector/inspector.ts +17 -0
  116. package/src/inspector/messaging.ts +30 -0
  117. package/src/lib/apiBase.ts +15 -0
  118. package/src/lib/classifyElement.ts +430 -0
  119. package/src/lib/claude-bin.ts +197 -0
  120. package/src/lib/claude-stream.ts +158 -0
  121. package/src/lib/clientProjectScanner.ts +344 -0
  122. package/src/lib/componentMatcher.ts +156 -0
  123. package/src/lib/constants.ts +573 -0
  124. package/src/lib/cssVariableUtils.ts +409 -0
  125. package/src/lib/diffParser.ts +206 -0
  126. package/src/lib/folderPicker.ts +84 -0
  127. package/src/lib/gradientParser.ts +160 -0
  128. package/src/lib/projectScanner.ts +355 -0
  129. package/src/lib/promptBuilder.ts +402 -0
  130. package/src/lib/shadowParser.ts +124 -0
  131. package/src/lib/tailwindClassParser.ts +248 -0
  132. package/src/lib/textShadowUtils.ts +106 -0
  133. package/src/lib/utils.ts +299 -0
  134. package/src/lib/validatePath.ts +40 -0
  135. package/src/proxy.ts +92 -0
  136. package/src/server/terminal-server.ts +104 -0
  137. package/src/store/changeSlice.ts +288 -0
  138. package/src/store/claudeSlice.ts +222 -0
  139. package/src/store/componentSlice.ts +90 -0
  140. package/src/store/consoleSlice.ts +51 -0
  141. package/src/store/cssVariableSlice.ts +94 -0
  142. package/src/store/elementSlice.ts +78 -0
  143. package/src/store/index.ts +35 -0
  144. package/src/store/terminalSlice.ts +30 -0
  145. package/src/store/treeSlice.ts +69 -0
  146. package/src/store/uiSlice.ts +327 -0
  147. package/src/types/changelog.ts +49 -0
  148. package/src/types/claude.ts +131 -0
  149. package/src/types/component.ts +49 -0
  150. package/src/types/cssVariables.ts +18 -0
  151. package/src/types/element.ts +21 -0
  152. package/src/types/file-system-access.d.ts +27 -0
  153. package/src/types/gradient.ts +12 -0
  154. package/src/types/messages.ts +392 -0
  155. package/src/types/shadow.ts +8 -0
  156. package/src/types/tree.ts +9 -0
  157. package/tsconfig.json +42 -0
  158. package/tsconfig.server.json +12 -0
@@ -0,0 +1,1872 @@
1
+ /**
2
+ * pAInt Inspector — Standalone Script
3
+ *
4
+ * Add this script to your project to enable pAInt inspection.
5
+ * Works with both local and Vercel-hosted editors automatically.
6
+ *
7
+ * Next.js App Router:
8
+ * <script src="http://localhost:4000/dev-editor-inspector.js" />
9
+ * (place inside <body> in layout.tsx, after all other content)
10
+ *
11
+ * Plain HTML:
12
+ * <script src="http://localhost:4000/dev-editor-inspector.js"></script>
13
+ * (place before </body>)
14
+ *
15
+ * The script auto-detects the editor origin from its src attribute.
16
+ * If detection fails, it discovers the editor via handshake.
17
+ * It does nothing when the page is not loaded inside an iframe.
18
+ */
19
+ ;(function () {
20
+ // Iframe guard — script does nothing if not loaded inside an iframe
21
+ if (window.parent === window) return
22
+
23
+ // --- Origin Detection ---
24
+ // Determine the editor (parent) window's origin for postMessage.
25
+ // Strategy: try same-origin access first (works when loaded via proxy),
26
+ // then fall back to '*' (works for cross-origin / direct URL loading).
27
+ // Note: postMessage does NOT throw on origin mismatch — it silently
28
+ // drops the message. So we must detect the correct origin upfront.
29
+ var parentOrigin = '*'
30
+ try {
31
+ // Same-origin: can access parent's location directly
32
+ parentOrigin = window.parent.location.origin
33
+ } catch (e) {
34
+ // Cross-origin: can't access parent's origin.
35
+ // Use '*' — safe for localhost dev tool use case.
36
+ parentOrigin = '*'
37
+ }
38
+
39
+ // Track whether the editor has acknowledged our connection
40
+ var connected = false
41
+
42
+ function send(message) {
43
+ try {
44
+ window.parent.postMessage(message, parentOrigin)
45
+ } catch (e) {}
46
+ }
47
+
48
+ // --- Console Interception (no DOM dependency, runs immediately) ---
49
+ var originalConsole = {
50
+ log: console.log,
51
+ info: console.info,
52
+ warn: console.warn,
53
+ error: console.error,
54
+ }
55
+
56
+ function serializeArg(arg) {
57
+ if (arg === null) return 'null'
58
+ if (arg === undefined) return 'undefined'
59
+ if (typeof arg === 'string') return arg
60
+ if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg)
61
+ if (arg instanceof Error) return arg.stack || arg.message || String(arg)
62
+ try {
63
+ return JSON.stringify(arg)
64
+ } catch (e) {
65
+ return String(arg)
66
+ }
67
+ }
68
+
69
+ function interceptConsole(level) {
70
+ console[level] = function () {
71
+ var args = []
72
+ for (var i = 0; i < arguments.length; i++) {
73
+ args.push(serializeArg(arguments[i]))
74
+ }
75
+ originalConsole[level].apply(console, arguments)
76
+ send({
77
+ type: 'CONSOLE_MESSAGE',
78
+ payload: { level: level, args: args, timestamp: Date.now() },
79
+ })
80
+ }
81
+ }
82
+
83
+ interceptConsole('log')
84
+ interceptConsole('info')
85
+ interceptConsole('warn')
86
+ interceptConsole('error')
87
+
88
+ window.onerror = function (message, source, line, column) {
89
+ send({
90
+ type: 'CONSOLE_MESSAGE',
91
+ payload: {
92
+ level: 'error',
93
+ args: [String(message)],
94
+ timestamp: Date.now(),
95
+ source: source || undefined,
96
+ line: line || undefined,
97
+ column: column || undefined,
98
+ },
99
+ })
100
+ }
101
+
102
+ window.addEventListener('unhandledrejection', function (e) {
103
+ var reason = e.reason
104
+ var msg =
105
+ reason instanceof Error
106
+ ? reason.stack || reason.message || String(reason)
107
+ : String(reason)
108
+ send({
109
+ type: 'CONSOLE_MESSAGE',
110
+ payload: {
111
+ level: 'error',
112
+ args: ['Unhandled Promise Rejection: ' + msg],
113
+ timestamp: Date.now(),
114
+ },
115
+ })
116
+ })
117
+
118
+ // --- Inspector initialization (requires document.body) ---
119
+ function initInspector() {
120
+ function kebabToCamel(str) {
121
+ return str.replace(/-([a-z])/g, function (m, c) {
122
+ return c.toUpperCase()
123
+ })
124
+ }
125
+
126
+ function camelToKebab(str) {
127
+ return str.replace(/[A-Z]/g, function (c) {
128
+ return '-' + c.toLowerCase()
129
+ })
130
+ }
131
+
132
+ function generateSelectorPath(element) {
133
+ var parts = []
134
+ var current = element
135
+ while (current && current !== document.documentElement) {
136
+ var selector = current.tagName.toLowerCase()
137
+ if (current.id) {
138
+ selector += '#' + CSS.escape(current.id)
139
+ parts.unshift(selector)
140
+ break
141
+ }
142
+ if (current.className && typeof current.className === 'string') {
143
+ var classes = current.className.trim().split(/\s+/).filter(Boolean)
144
+ if (classes.length > 0) {
145
+ selector +=
146
+ '.' +
147
+ classes
148
+ .map(function (c) {
149
+ return CSS.escape(c)
150
+ })
151
+ .join('.')
152
+ }
153
+ }
154
+ var parent = current.parentElement
155
+ if (parent) {
156
+ var siblings = Array.from(parent.children).filter(function (c) {
157
+ return c.tagName === current.tagName
158
+ })
159
+ if (siblings.length > 1) {
160
+ var index = siblings.indexOf(current) + 1
161
+ selector += ':nth-of-type(' + index + ')'
162
+ }
163
+ }
164
+ parts.unshift(selector)
165
+ current = current.parentElement
166
+ }
167
+ return parts.join(' > ')
168
+ }
169
+
170
+ function serializeTree(element) {
171
+ if (!element || element.nodeType !== 1) return null
172
+ var tagName = element.tagName.toLowerCase()
173
+ if (tagName === 'script' || tagName === 'style' || tagName === 'link')
174
+ return null
175
+ var children = []
176
+ for (var i = 0; i < element.children.length; i++) {
177
+ var child = serializeTree(element.children[i])
178
+ if (child) children.push(child)
179
+ }
180
+ return {
181
+ id: generateSelectorPath(element),
182
+ tagName: tagName,
183
+ className:
184
+ element.className && typeof element.className === 'string'
185
+ ? element.className
186
+ : null,
187
+ elementId: element.id || null,
188
+ children: children,
189
+ }
190
+ }
191
+
192
+ function getComputedStylesForElement(el) {
193
+ var computed = window.getComputedStyle(el)
194
+ var props = [
195
+ 'width',
196
+ 'height',
197
+ 'min-width',
198
+ 'min-height',
199
+ 'max-width',
200
+ 'max-height',
201
+ 'overflow',
202
+ 'margin-top',
203
+ 'margin-right',
204
+ 'margin-bottom',
205
+ 'margin-left',
206
+ 'padding-top',
207
+ 'padding-right',
208
+ 'padding-bottom',
209
+ 'padding-left',
210
+ 'font-family',
211
+ 'font-size',
212
+ 'font-weight',
213
+ 'line-height',
214
+ 'letter-spacing',
215
+ 'text-align',
216
+ 'text-decoration',
217
+ 'text-transform',
218
+ 'color',
219
+ 'border-width',
220
+ 'border-style',
221
+ 'border-color',
222
+ 'border-radius',
223
+ 'border-top-left-radius',
224
+ 'border-top-right-radius',
225
+ 'border-bottom-right-radius',
226
+ 'border-bottom-left-radius',
227
+ 'background-color',
228
+ 'background-image',
229
+ 'opacity',
230
+ 'display',
231
+ 'flex-direction',
232
+ 'justify-content',
233
+ 'align-items',
234
+ 'flex-wrap',
235
+ 'gap',
236
+ 'column-gap',
237
+ 'row-gap',
238
+ 'grid-template-columns',
239
+ 'grid-template-rows',
240
+ 'grid-auto-flow',
241
+ 'justify-items',
242
+ 'vertical-align',
243
+ 'position',
244
+ 'top',
245
+ 'right',
246
+ 'bottom',
247
+ 'left',
248
+ 'z-index',
249
+ 'box-sizing',
250
+ 'fill',
251
+ 'stroke',
252
+ ]
253
+ var styles = {}
254
+ for (var i = 0; i < props.length; i++) {
255
+ styles[kebabToCamel(props[i])] = computed.getPropertyValue(props[i])
256
+ }
257
+ return styles
258
+ }
259
+
260
+ function scanCSSVariableDefinitions() {
261
+ var definitions = {}
262
+ var rootStyles = window.getComputedStyle(document.documentElement)
263
+ var FRAMEWORK_PREFIXES = [
264
+ '--tw-',
265
+ '--next-',
266
+ '--radix-',
267
+ '--chakra-',
268
+ '--mantine-',
269
+ '--mui-',
270
+ '--framer-',
271
+ '--sb-',
272
+ ]
273
+
274
+ function extractFromRules(rules) {
275
+ for (var ri = 0; ri < rules.length; ri++) {
276
+ var rule = rules[ri]
277
+ // Recurse into @media and other grouping rules
278
+ if (rule.cssRules) {
279
+ extractFromRules(rule.cssRules)
280
+ continue
281
+ }
282
+ if (!rule.style) continue
283
+ for (var pi = 0; pi < rule.style.length; pi++) {
284
+ var prop = rule.style[pi]
285
+ if (prop.indexOf('--') === 0) {
286
+ var rawVal = rule.style.getPropertyValue(prop).trim()
287
+ var resolved = rootStyles.getPropertyValue(prop).trim()
288
+ definitions[prop] = {
289
+ value: rawVal,
290
+ resolvedValue: resolved || rawVal,
291
+ selector: rule.selectorText || '',
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ // Primary: check for <style data-design-tokens> or <link data-design-tokens>
299
+ var taggedSheets = []
300
+ for (var si = 0; si < document.styleSheets.length; si++) {
301
+ var sheet = document.styleSheets[si]
302
+ if (
303
+ sheet.ownerNode &&
304
+ sheet.ownerNode.hasAttribute &&
305
+ sheet.ownerNode.hasAttribute('data-design-tokens')
306
+ ) {
307
+ taggedSheets.push(sheet)
308
+ }
309
+ }
310
+
311
+ if (taggedSheets.length > 0) {
312
+ // Explicit mode: scan only tagged sheets
313
+ for (var ti = 0; ti < taggedSheets.length; ti++) {
314
+ var taggedRules
315
+ try {
316
+ taggedRules = taggedSheets[ti].cssRules || taggedSheets[ti].rules
317
+ } catch (e) {
318
+ continue
319
+ }
320
+ if (taggedRules) extractFromRules(taggedRules)
321
+ }
322
+ return { definitions: definitions, isExplicit: true }
323
+ }
324
+
325
+ // Fallback: scan all sheets
326
+ for (var fi = 0; fi < document.styleSheets.length; fi++) {
327
+ var fallbackSheet = document.styleSheets[fi]
328
+ var fallbackRules
329
+ try {
330
+ fallbackRules = fallbackSheet.cssRules || fallbackSheet.rules
331
+ } catch (e) {
332
+ continue
333
+ }
334
+ if (fallbackRules) extractFromRules(fallbackRules)
335
+ }
336
+
337
+ // Check for <meta name="design-tokens-prefix"> allowlist
338
+ var metaEl = document.querySelector('meta[name="design-tokens-prefix"]')
339
+ var metaPrefixes = null
340
+ if (metaEl) {
341
+ var content = metaEl.getAttribute('content')
342
+ if (content) {
343
+ metaPrefixes = content.split(',')
344
+ for (var mpi = 0; mpi < metaPrefixes.length; mpi++) {
345
+ metaPrefixes[mpi] = metaPrefixes[mpi].trim()
346
+ }
347
+ }
348
+ }
349
+
350
+ // Filter definitions
351
+ var filtered = {}
352
+ var keys = Object.keys(definitions)
353
+ for (var ki = 0; ki < keys.length; ki++) {
354
+ var key = keys[ki]
355
+ if (metaPrefixes) {
356
+ // Meta allowlist mode: only keep variables matching specified prefixes
357
+ var allowed = false
358
+ for (var api = 0; api < metaPrefixes.length; api++) {
359
+ if (key.indexOf(metaPrefixes[api]) === 0) {
360
+ allowed = true
361
+ break
362
+ }
363
+ }
364
+ if (allowed) filtered[key] = definitions[key]
365
+ } else {
366
+ // Default fallback: filter out known framework prefixes
367
+ var isFramework = false
368
+ for (var fpi = 0; fpi < FRAMEWORK_PREFIXES.length; fpi++) {
369
+ if (key.indexOf(FRAMEWORK_PREFIXES[fpi]) === 0) {
370
+ isFramework = true
371
+ break
372
+ }
373
+ }
374
+ if (!isFramework) filtered[key] = definitions[key]
375
+ }
376
+ }
377
+
378
+ return { definitions: filtered, isExplicit: false }
379
+ }
380
+
381
+ function detectCSSVariablesOnElement(el) {
382
+ var usages = {}
383
+ if (el.style) {
384
+ for (var si = 0; si < el.style.length; si++) {
385
+ var inlineProp = el.style[si]
386
+ var inlineVal = el.style.getPropertyValue(inlineProp)
387
+ if (inlineVal && inlineVal.indexOf('var(') >= 0) {
388
+ usages[inlineProp] = inlineVal.trim()
389
+ }
390
+ }
391
+ }
392
+ for (var shi = 0; shi < document.styleSheets.length; shi++) {
393
+ var sheet = document.styleSheets[shi]
394
+ var rules
395
+ try {
396
+ rules = sheet.cssRules || sheet.rules
397
+ } catch (e) {
398
+ continue
399
+ }
400
+ if (!rules) continue
401
+ for (var ri = 0; ri < rules.length; ri++) {
402
+ var rule = rules[ri]
403
+ if (!rule.selectorText || !rule.style) continue
404
+ var matches = false
405
+ try {
406
+ matches = el.matches(rule.selectorText)
407
+ } catch (e) {
408
+ continue
409
+ }
410
+ if (!matches) continue
411
+ for (var pi = 0; pi < rule.style.length; pi++) {
412
+ var prop = rule.style[pi]
413
+ var val = rule.style.getPropertyValue(prop)
414
+ if (val && val.indexOf('var(') >= 0) {
415
+ if (!usages[prop]) {
416
+ usages[prop] = val.trim()
417
+ }
418
+ }
419
+ }
420
+ }
421
+ }
422
+ return usages
423
+ }
424
+
425
+ // Selection highlight
426
+ var selectionOverlay = document.createElement('div')
427
+ selectionOverlay.style.cssText =
428
+ 'position:fixed;pointer-events:none;z-index:999997;border:2px solid #4a9eff;display:none;'
429
+ document.body.appendChild(selectionOverlay)
430
+
431
+ // Hover highlight — dashed green border + element name label
432
+ var hoverOverlay = document.createElement('div')
433
+ hoverOverlay.style.cssText =
434
+ 'position:fixed;pointer-events:none;z-index:999996;border:1px dashed #4ade80;display:none;transition:top 0.04s,left 0.04s,width 0.04s,height 0.04s;'
435
+ document.body.appendChild(hoverOverlay)
436
+
437
+ var hoverLabel = document.createElement('div')
438
+ hoverLabel.style.cssText =
439
+ 'position:absolute;top:-18px;left:-1px;padding:1px 6px;font-size:10px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;line-height:14px;color:#fff;background:#1D3F23;border-radius:3px 3px 0 0;white-space:nowrap;pointer-events:none;'
440
+ hoverOverlay.appendChild(hoverLabel)
441
+
442
+ var hoveredElement = null
443
+
444
+ function getElementLabel(el) {
445
+ var tag = el.tagName.toLowerCase()
446
+ if (el.id) return tag + '#' + el.id
447
+ var cls = el.className
448
+ if (cls && typeof cls === 'string') {
449
+ var classes = cls.trim().split(/\s+/)
450
+ // Prefer c- prefixed class for the label
451
+ for (var i = 0; i < classes.length; i++) {
452
+ if (classes[i].indexOf('c-') === 0) return tag + '.' + classes[i]
453
+ }
454
+ if (classes[0]) return tag + '.' + classes[0]
455
+ }
456
+ return tag
457
+ }
458
+
459
+ document.addEventListener('mousemove', function (e) {
460
+ if (!selectionModeEnabled) {
461
+ hoverOverlay.style.display = 'none'
462
+ return
463
+ }
464
+ var el = document.elementFromPoint(e.clientX, e.clientY)
465
+ if (
466
+ !el ||
467
+ el === hoverOverlay ||
468
+ el === selectionOverlay ||
469
+ el === hoverLabel
470
+ )
471
+ return
472
+ if (el === selectedElement) {
473
+ hoverOverlay.style.display = 'none'
474
+ hoveredElement = null
475
+ return
476
+ }
477
+ if (el === hoveredElement) return
478
+ hoveredElement = el
479
+ var rect = el.getBoundingClientRect()
480
+ hoverOverlay.style.display = 'block'
481
+ hoverOverlay.style.top = rect.top + 'px'
482
+ hoverOverlay.style.left = rect.left + 'px'
483
+ hoverOverlay.style.width = rect.width + 'px'
484
+ hoverOverlay.style.height = rect.height + 'px'
485
+ hoverLabel.textContent = getElementLabel(el)
486
+ if (rect.top < 20) {
487
+ hoverLabel.style.top = 'auto'
488
+ hoverLabel.style.bottom = '-18px'
489
+ hoverLabel.style.borderRadius = '0 0 3px 3px'
490
+ } else {
491
+ hoverLabel.style.top = '-18px'
492
+ hoverLabel.style.bottom = 'auto'
493
+ hoverLabel.style.borderRadius = '3px 3px 0 0'
494
+ }
495
+ })
496
+
497
+ var selectedElement = null
498
+ var selectionModeEnabled = true
499
+
500
+ // Click selection — when selection mode is on, intercept clicks to select elements.
501
+ // When off, let clicks through so links and buttons work normally.
502
+ document.addEventListener(
503
+ 'click',
504
+ function (e) {
505
+ if (!selectionModeEnabled) return
506
+
507
+ // If text editing is active, clicking outside commits the edit
508
+ if (textEditingActive) {
509
+ var clickedEl = document.elementFromPoint(e.clientX, e.clientY)
510
+ if (clickedEl !== textEditTarget) {
511
+ e.preventDefault()
512
+ e.stopPropagation()
513
+ commitTextEdit()
514
+ }
515
+ return
516
+ }
517
+
518
+ e.preventDefault()
519
+ e.stopPropagation()
520
+ var el = document.elementFromPoint(e.clientX, e.clientY)
521
+ if (
522
+ !el ||
523
+ el === selectionOverlay ||
524
+ el === hoverOverlay ||
525
+ el === hoverLabel
526
+ )
527
+ return
528
+ hoverOverlay.style.display = 'none'
529
+ selectElement(el)
530
+ },
531
+ true,
532
+ )
533
+
534
+ function selectElement(el) {
535
+ // Don't select elements when selection mode is disabled (preview mode)
536
+ if (!selectionModeEnabled) return
537
+ selectedElement = el
538
+ var rect = el.getBoundingClientRect()
539
+ selectionOverlay.style.display = 'block'
540
+ selectionOverlay.style.top = rect.top + 'px'
541
+ selectionOverlay.style.left = rect.left + 'px'
542
+ selectionOverlay.style.width = rect.width + 'px'
543
+ selectionOverlay.style.height = rect.height + 'px'
544
+
545
+ var attrs = {}
546
+ for (var ai = 0; ai < el.attributes.length; ai++) {
547
+ var attr = el.attributes[ai]
548
+ attrs[attr.name] = attr.value
549
+ }
550
+
551
+ var text = (el.innerText || '').substring(0, 500) || null
552
+ var varUsages = detectCSSVariablesOnElement(el)
553
+
554
+ send({
555
+ type: 'ELEMENT_SELECTED',
556
+ payload: {
557
+ selectorPath: generateSelectorPath(el),
558
+ tagName: el.tagName.toLowerCase(),
559
+ className:
560
+ el.className && typeof el.className === 'string'
561
+ ? el.className
562
+ : null,
563
+ id: el.id || null,
564
+ attributes: attrs,
565
+ innerText: text,
566
+ computedStyles: getComputedStylesForElement(el),
567
+ cssVariableUsages: varUsages,
568
+ boundingRect: {
569
+ x: rect.x,
570
+ y: rect.y,
571
+ width: rect.width,
572
+ height: rect.height,
573
+ top: rect.top,
574
+ right: rect.right,
575
+ bottom: rect.bottom,
576
+ left: rect.left,
577
+ },
578
+ },
579
+ })
580
+ }
581
+
582
+ function clearSelection() {
583
+ selectedElement = null
584
+ selectionOverlay.style.display = 'none'
585
+ }
586
+
587
+ function updateSelectionOverlay() {
588
+ if (!selectedElement || selectionOverlay.style.display === 'none') return
589
+ var rect = selectedElement.getBoundingClientRect()
590
+ selectionOverlay.style.top = rect.top + 'px'
591
+ selectionOverlay.style.left = rect.left + 'px'
592
+ selectionOverlay.style.width = rect.width + 'px'
593
+ selectionOverlay.style.height = rect.height + 'px'
594
+ }
595
+ window.addEventListener('scroll', updateSelectionOverlay, true)
596
+ window.addEventListener('resize', updateSelectionOverlay, true)
597
+
598
+ // --- Inline Text Editing ---
599
+ var textEditingActive = false
600
+ var originalTextContent = null
601
+ var textEditTarget = null
602
+ var SKIP_TEXT_EDIT_TAGS = {
603
+ INPUT: 1,
604
+ TEXTAREA: 1,
605
+ SELECT: 1,
606
+ IMG: 1,
607
+ VIDEO: 1,
608
+ IFRAME: 1,
609
+ SVG: 1,
610
+ svg: 1,
611
+ CANVAS: 1,
612
+ }
613
+
614
+ function commitTextEdit() {
615
+ if (!textEditingActive || !textEditTarget) return
616
+ var newText = textEditTarget.textContent || ''
617
+ var el = textEditTarget
618
+ var selectorPath = generateSelectorPath(el)
619
+
620
+ el.contentEditable = 'false'
621
+ el.style.removeProperty('outline')
622
+ el.style.removeProperty('outline-offset')
623
+ el.style.removeProperty('min-width')
624
+ textEditingActive = false
625
+ textEditTarget = null
626
+
627
+ observer.observe(document.body, {
628
+ childList: true,
629
+ subtree: true,
630
+ attributes: true,
631
+ attributeFilter: ['class', 'id', 'style'],
632
+ })
633
+
634
+ selectionOverlay.style.display = 'block'
635
+ updateSelectionOverlay()
636
+
637
+ if (newText !== originalTextContent) {
638
+ send({
639
+ type: 'TEXT_CHANGED',
640
+ payload: {
641
+ selectorPath: selectorPath,
642
+ originalText: originalTextContent || '',
643
+ newText: newText,
644
+ },
645
+ })
646
+ }
647
+ originalTextContent = null
648
+ }
649
+
650
+ function cancelTextEdit() {
651
+ if (!textEditingActive || !textEditTarget) return
652
+ textEditTarget.textContent = originalTextContent
653
+ textEditTarget.contentEditable = 'false'
654
+ textEditTarget.style.removeProperty('outline')
655
+ textEditTarget.style.removeProperty('outline-offset')
656
+ textEditTarget.style.removeProperty('min-width')
657
+ textEditingActive = false
658
+ textEditTarget = null
659
+ originalTextContent = null
660
+
661
+ observer.observe(document.body, {
662
+ childList: true,
663
+ subtree: true,
664
+ attributes: true,
665
+ attributeFilter: ['class', 'id', 'style'],
666
+ })
667
+
668
+ selectionOverlay.style.display = 'block'
669
+ updateSelectionOverlay()
670
+ }
671
+
672
+ // Find the best editable text target from a starting element.
673
+ // Walks down through wrappers to find a leaf text node,
674
+ // or returns the element itself if it has direct text content.
675
+ // Handles: <button><svg/>Submit</button>, <a><svg/><span>text</span><svg/></a>, etc.
676
+ function findTextTarget(el) {
677
+ if (!el || SKIP_TEXT_EDIT_TAGS[el.tagName]) return null
678
+ // Leaf node with text — ideal target
679
+ if (el.children.length === 0) {
680
+ return el.textContent && el.textContent.trim() ? el : null
681
+ }
682
+ // Single child — recurse into it (common: <button><span>text</span></button>)
683
+ if (el.children.length === 1) {
684
+ var child = el.children[0]
685
+ if (SKIP_TEXT_EDIT_TAGS[child.tagName]) {
686
+ return hasDirectTextNodes(el) ? el : null
687
+ }
688
+ return findTextTarget(child)
689
+ }
690
+ // Multiple children — find the single non-skippable text-bearing child
691
+ // (handles: <a><svg/><span>Book a Call</span><svg/></a>)
692
+ var textChild = null
693
+ for (var i = 0; i < el.children.length; i++) {
694
+ var ch = el.children[i]
695
+ if (SKIP_TEXT_EDIT_TAGS[ch.tagName]) continue
696
+ if (ch.textContent && ch.textContent.trim()) {
697
+ if (textChild) {
698
+ textChild = null
699
+ break
700
+ } // ambiguous — multiple text children
701
+ textChild = ch
702
+ }
703
+ }
704
+ if (textChild) return findTextTarget(textChild)
705
+ // Fall back: allow editing if element has direct text nodes
706
+ return hasDirectTextNodes(el) ? el : null
707
+ }
708
+
709
+ function hasDirectTextNodes(el) {
710
+ for (var i = 0; i < el.childNodes.length; i++) {
711
+ if (
712
+ el.childNodes[i].nodeType === 3 &&
713
+ el.childNodes[i].textContent.trim()
714
+ ) {
715
+ return true
716
+ }
717
+ }
718
+ return false
719
+ }
720
+
721
+ document.addEventListener(
722
+ 'dblclick',
723
+ function (e) {
724
+ if (!selectionModeEnabled) return
725
+ e.preventDefault()
726
+ e.stopPropagation()
727
+
728
+ var el = document.elementFromPoint(e.clientX, e.clientY)
729
+ // If the selection overlay is in the way, temporarily hide it
730
+ // and re-probe to get the actual element underneath
731
+ if (el === selectionOverlay || el === hoverOverlay) {
732
+ var prevSel = selectionOverlay.style.display
733
+ var prevHov = hoverOverlay.style.display
734
+ selectionOverlay.style.display = 'none'
735
+ hoverOverlay.style.display = 'none'
736
+ el = document.elementFromPoint(e.clientX, e.clientY)
737
+ selectionOverlay.style.display = prevSel
738
+ hoverOverlay.style.display = prevHov
739
+ }
740
+ if (!el) return
741
+ if (SKIP_TEXT_EDIT_TAGS[el.tagName]) return
742
+
743
+ // Find the best editable text target (may walk down into children)
744
+ el = findTextTarget(el)
745
+ if (!el) return
746
+
747
+ var text = el.textContent
748
+ if (text === null || text === undefined) return
749
+
750
+ textEditingActive = true
751
+ textEditTarget = el
752
+ originalTextContent = text
753
+
754
+ observer.disconnect()
755
+
756
+ selectionOverlay.style.display = 'none'
757
+
758
+ el.contentEditable = 'true'
759
+ el.style.setProperty('outline', '2px solid #4a9eff', 'important')
760
+ el.style.setProperty('outline-offset', '2px', 'important')
761
+ el.style.setProperty('min-width', '20px', 'important')
762
+ el.focus()
763
+
764
+ var range = document.createRange()
765
+ range.selectNodeContents(el)
766
+ var sel = window.getSelection()
767
+ sel.removeAllRanges()
768
+ sel.addRange(range)
769
+ },
770
+ true,
771
+ )
772
+
773
+ document.addEventListener(
774
+ 'keydown',
775
+ function (e) {
776
+ if (textEditingActive) {
777
+ e.stopPropagation()
778
+ if (e.key === 'Enter' && !e.shiftKey) {
779
+ e.preventDefault()
780
+ commitTextEdit()
781
+ } else if (e.key === 'Escape') {
782
+ e.preventDefault()
783
+ cancelTextEdit()
784
+ }
785
+ return
786
+ }
787
+
788
+ // Delete selected element (Delete or Backspace when not editing text)
789
+ if (
790
+ (e.key === 'Delete' || e.key === 'Backspace') &&
791
+ selectionModeEnabled &&
792
+ selectedElement
793
+ ) {
794
+ e.preventDefault()
795
+ e.stopPropagation()
796
+ var delEl = selectedElement
797
+ var computed = window.getComputedStyle(delEl)
798
+ var origDisplay = computed.getPropertyValue('display')
799
+ var delSelector = generateSelectorPath(delEl)
800
+
801
+ var delAttrs = {}
802
+ for (var dai = 0; dai < delEl.attributes.length; dai++) {
803
+ var da = delEl.attributes[dai]
804
+ delAttrs[da.name] = da.value
805
+ }
806
+
807
+ // Hide the element
808
+ delEl.style.setProperty('display', 'none', 'important')
809
+ selectionOverlay.style.display = 'none'
810
+ selectedElement = null
811
+
812
+ send({
813
+ type: 'ELEMENT_DELETED',
814
+ payload: {
815
+ selectorPath: delSelector,
816
+ originalDisplay: origDisplay,
817
+ tagName: delEl.tagName.toLowerCase(),
818
+ className:
819
+ delEl.className && typeof delEl.className === 'string'
820
+ ? delEl.className
821
+ : null,
822
+ elementId: delEl.id || null,
823
+ innerText: (delEl.innerText || '').substring(0, 500) || null,
824
+ attributes: delAttrs,
825
+ computedStyles: getComputedStylesForElement(delEl),
826
+ },
827
+ })
828
+ }
829
+ },
830
+ true,
831
+ )
832
+
833
+ // --- Component Detection ---
834
+ var SEMANTIC_COMPONENTS = {
835
+ button: 'Button',
836
+ nav: 'Navigation',
837
+ input: 'Input',
838
+ header: 'Header',
839
+ footer: 'Footer',
840
+ dialog: 'Dialog',
841
+ a: 'Link',
842
+ img: 'Image',
843
+ form: 'Form',
844
+ select: 'Select',
845
+ textarea: 'Textarea',
846
+ table: 'Table',
847
+ aside: 'Sidebar',
848
+ main: 'Main Content',
849
+ section: 'Section',
850
+ }
851
+
852
+ var ARIA_ROLE_MAP = {
853
+ button: 'Button',
854
+ navigation: 'Navigation',
855
+ tab: 'Tab',
856
+ tablist: 'Tab List',
857
+ dialog: 'Dialog',
858
+ alert: 'Alert',
859
+ menu: 'Menu',
860
+ menuitem: 'Menu Item',
861
+ search: 'Search',
862
+ }
863
+
864
+ var CLASS_PATTERNS = [
865
+ [/\bbtn\b/i, 'Button'],
866
+ [/\bcard\b/i, 'Card'],
867
+ [/\bmodal\b/i, 'Modal'],
868
+ [/\bdropdown\b/i, 'Dropdown'],
869
+ [/\bbadge\b/i, 'Badge'],
870
+ [/\bnav\b/i, 'Navigation'],
871
+ [/\balert\b/i, 'Alert'],
872
+ [/\btabs?\b/i, 'Tab'],
873
+ ]
874
+
875
+ function detectCPrefixComponent(el) {
876
+ var cls = el.className
877
+ if (!cls || typeof cls !== 'string') return null
878
+ var classes = cls.trim().split(/\s+/)
879
+ for (var i = 0; i < classes.length; i++) {
880
+ if (classes[i].indexOf('c-') === 0 && classes[i].length > 2) {
881
+ var raw = classes[i].substring(2)
882
+ // Convert kebab-case to Title Case (e.g. "nav-bar" → "Nav Bar")
883
+ var name = raw
884
+ .split('-')
885
+ .map(function (part) {
886
+ return part.charAt(0).toUpperCase() + part.slice(1)
887
+ })
888
+ .join(' ')
889
+ return { name: name, method: 'c-prefix' }
890
+ }
891
+ }
892
+ return null
893
+ }
894
+
895
+ function detectSingleComponent(el) {
896
+ var tag = el.tagName.toLowerCase()
897
+ var dataComp = el.getAttribute('data-component')
898
+ if (dataComp) return { name: dataComp, method: 'data-attribute' }
899
+ // Detect c- prefixed class identifiers (e.g. "c-header" → "Header")
900
+ var cPrefix = detectCPrefixComponent(el)
901
+ if (cPrefix) return cPrefix
902
+ if (tag.indexOf('-') >= 0) return { name: tag, method: 'custom-element' }
903
+ if (SEMANTIC_COMPONENTS[tag])
904
+ return { name: SEMANTIC_COMPONENTS[tag], method: 'semantic-html' }
905
+ var role = el.getAttribute('role')
906
+ if (role && ARIA_ROLE_MAP[role])
907
+ return { name: ARIA_ROLE_MAP[role], method: 'aria-role' }
908
+ var cls = el.className
909
+ if (cls && typeof cls === 'string') {
910
+ for (var pi = 0; pi < CLASS_PATTERNS.length; pi++) {
911
+ if (CLASS_PATTERNS[pi][0].test(cls))
912
+ return { name: CLASS_PATTERNS[pi][1], method: 'class-pattern' }
913
+ }
914
+ }
915
+ return null
916
+ }
917
+
918
+ var SIZE_SUFFIXES = ['xs', 'sm', 'md', 'lg', 'xl', '2xl']
919
+ var COLOR_SUFFIXES = [
920
+ 'primary',
921
+ 'secondary',
922
+ 'success',
923
+ 'danger',
924
+ 'warning',
925
+ 'info',
926
+ 'light',
927
+ 'dark',
928
+ ]
929
+ var STATE_SUFFIXES = [
930
+ 'active',
931
+ 'disabled',
932
+ 'loading',
933
+ 'selected',
934
+ 'checked',
935
+ ]
936
+
937
+ function detectClassVariants(el) {
938
+ var groups = []
939
+ var cls = el.className
940
+ if (!cls || typeof cls !== 'string') return groups
941
+ var classes = cls.trim().split(/\s+/).filter(Boolean)
942
+ var prefixes = {}
943
+ for (var ci = 0; ci < classes.length; ci++) {
944
+ var parts = classes[ci].split('-')
945
+ if (parts.length >= 2) {
946
+ var base = parts[0]
947
+ var suffix = parts.slice(1).join('-')
948
+ if (!prefixes[base])
949
+ prefixes[base] = { currentClass: classes[ci], suffix: suffix }
950
+ }
951
+ }
952
+ for (var base in prefixes) {
953
+ if (!prefixes.hasOwnProperty(base)) continue
954
+ var sizeOpts = [],
955
+ colorOpts = [],
956
+ stateOpts = []
957
+ var currentClass = prefixes[base].currentClass
958
+ for (var si = 0; si < document.styleSheets.length; si++) {
959
+ var sheet = document.styleSheets[si]
960
+ var rules
961
+ try {
962
+ rules = sheet.cssRules || sheet.rules
963
+ } catch (e) {
964
+ continue
965
+ }
966
+ if (!rules) continue
967
+ for (var ri = 0; ri < rules.length; ri++) {
968
+ var rule = rules[ri]
969
+ if (!rule.selectorText) continue
970
+ var escapedBase = base.replace(/[-\/\\^*+?.()|[\]]/g, '\\$&')
971
+ var match = rule.selectorText.match(
972
+ new RegExp('\\.' + escapedBase + '-([\\w-]+)'),
973
+ )
974
+ if (!match) continue
975
+ var foundSuffix = match[1]
976
+ var foundClass = base + '-' + foundSuffix
977
+ if (foundClass === currentClass) continue
978
+ var opt = {
979
+ label: foundSuffix,
980
+ className: foundClass,
981
+ removeClassNames: [currentClass],
982
+ pseudoState: null,
983
+ pseudoStyles: null,
984
+ }
985
+ if (SIZE_SUFFIXES.indexOf(foundSuffix) >= 0) {
986
+ sizeOpts.push(opt)
987
+ } else if (COLOR_SUFFIXES.indexOf(foundSuffix) >= 0) {
988
+ colorOpts.push(opt)
989
+ } else if (STATE_SUFFIXES.indexOf(foundSuffix) >= 0) {
990
+ stateOpts.push(opt)
991
+ }
992
+ }
993
+ }
994
+ var currentSuffix = prefixes[base].suffix
995
+ var currentOpt = {
996
+ label: currentSuffix,
997
+ className: currentClass,
998
+ removeClassNames: [],
999
+ pseudoState: null,
1000
+ pseudoStyles: null,
1001
+ }
1002
+ if (sizeOpts.length > 0) {
1003
+ sizeOpts.unshift(currentOpt)
1004
+ var seen = {}
1005
+ sizeOpts = sizeOpts.filter(function (o) {
1006
+ if (seen[o.className]) return false
1007
+ seen[o.className] = true
1008
+ return true
1009
+ })
1010
+ if (sizeOpts.length >= 2) {
1011
+ for (var soi = 0; soi < sizeOpts.length; soi++) {
1012
+ sizeOpts[soi].removeClassNames = [currentClass]
1013
+ }
1014
+ sizeOpts[0].removeClassNames = []
1015
+ groups.push({
1016
+ groupName: 'Size',
1017
+ type: 'class',
1018
+ options: sizeOpts,
1019
+ activeIndex: 0,
1020
+ })
1021
+ }
1022
+ }
1023
+ if (colorOpts.length > 0) {
1024
+ colorOpts.unshift(currentOpt)
1025
+ var seenC = {}
1026
+ colorOpts = colorOpts.filter(function (o) {
1027
+ if (seenC[o.className]) return false
1028
+ seenC[o.className] = true
1029
+ return true
1030
+ })
1031
+ if (colorOpts.length >= 2) {
1032
+ for (var coi = 0; coi < colorOpts.length; coi++) {
1033
+ colorOpts[coi].removeClassNames = [currentClass]
1034
+ }
1035
+ colorOpts[0].removeClassNames = []
1036
+ groups.push({
1037
+ groupName: 'Color',
1038
+ type: 'class',
1039
+ options: colorOpts,
1040
+ activeIndex: 0,
1041
+ })
1042
+ }
1043
+ }
1044
+ if (stateOpts.length > 0) {
1045
+ stateOpts.unshift(currentOpt)
1046
+ var seenS = {}
1047
+ stateOpts = stateOpts.filter(function (o) {
1048
+ if (seenS[o.className]) return false
1049
+ seenS[o.className] = true
1050
+ return true
1051
+ })
1052
+ if (stateOpts.length >= 2) {
1053
+ for (var stoi = 0; stoi < stateOpts.length; stoi++) {
1054
+ stateOpts[stoi].removeClassNames = [currentClass]
1055
+ }
1056
+ stateOpts[0].removeClassNames = []
1057
+ groups.push({
1058
+ groupName: 'State',
1059
+ type: 'class',
1060
+ options: stateOpts,
1061
+ activeIndex: 0,
1062
+ })
1063
+ }
1064
+ }
1065
+ }
1066
+ return groups
1067
+ }
1068
+
1069
+ function detectPseudoVariants(el) {
1070
+ try {
1071
+ var visualProps = [
1072
+ 'color',
1073
+ 'backgroundColor',
1074
+ 'borderColor',
1075
+ 'opacity',
1076
+ 'transform',
1077
+ 'boxShadow',
1078
+ 'textDecoration',
1079
+ 'outline',
1080
+ ]
1081
+ var defaultStyles = window.getComputedStyle(el)
1082
+ var pseudos = ['hover', 'focus', 'active']
1083
+ var options = [
1084
+ {
1085
+ label: 'default',
1086
+ className: null,
1087
+ removeClassNames: null,
1088
+ pseudoState: null,
1089
+ pseudoStyles: null,
1090
+ },
1091
+ ]
1092
+ for (var pi = 0; pi < pseudos.length; pi++) {
1093
+ var pseudo = pseudos[pi]
1094
+ var pseudoStyles = window.getComputedStyle(el, ':' + pseudo)
1095
+ var diffs = {}
1096
+ var hasDiff = false
1097
+ for (var vi = 0; vi < visualProps.length; vi++) {
1098
+ var prop = visualProps[vi]
1099
+ var defaultVal = defaultStyles.getPropertyValue(prop)
1100
+ var pseudoVal = pseudoStyles.getPropertyValue(prop)
1101
+ if (defaultVal !== pseudoVal && pseudoVal) {
1102
+ diffs[prop] = pseudoVal
1103
+ hasDiff = true
1104
+ }
1105
+ }
1106
+ if (hasDiff) {
1107
+ options.push({
1108
+ label: pseudo,
1109
+ className: null,
1110
+ removeClassNames: null,
1111
+ pseudoState: pseudo,
1112
+ pseudoStyles: diffs,
1113
+ })
1114
+ }
1115
+ }
1116
+ if (options.length >= 2) {
1117
+ return [
1118
+ {
1119
+ groupName: 'Pseudo States',
1120
+ type: 'pseudo',
1121
+ options: options,
1122
+ activeIndex: 0,
1123
+ },
1124
+ ]
1125
+ }
1126
+ } catch (e) {}
1127
+ return []
1128
+ }
1129
+
1130
+ function scanForComponents(rootElement) {
1131
+ var allElements = rootElement.querySelectorAll('*')
1132
+ var results = []
1133
+ var batchSize = 50
1134
+ var index = 0
1135
+ var scheduleNext =
1136
+ typeof requestIdleCallback === 'function'
1137
+ ? function (cb) {
1138
+ requestIdleCallback(cb)
1139
+ }
1140
+ : function (cb) {
1141
+ setTimeout(cb, 0)
1142
+ }
1143
+
1144
+ function processBatch() {
1145
+ var end = Math.min(index + batchSize, allElements.length)
1146
+ for (var i = index; i < end; i++) {
1147
+ var el = allElements[i]
1148
+ var detection = detectSingleComponent(el)
1149
+ if (detection) {
1150
+ var rect = el.getBoundingClientRect()
1151
+ var text = (el.innerText || '').substring(0, 50) || null
1152
+ var childCount = 0
1153
+ var childEls = el.querySelectorAll('*')
1154
+ for (var ci = 0; ci < childEls.length; ci++) {
1155
+ if (detectSingleComponent(childEls[ci])) childCount++
1156
+ }
1157
+ results.push({
1158
+ selectorPath: generateSelectorPath(el),
1159
+ name: detection.name,
1160
+ tagName: el.tagName.toLowerCase(),
1161
+ detectionMethod: detection.method,
1162
+ className:
1163
+ el.className && typeof el.className === 'string'
1164
+ ? el.className
1165
+ : null,
1166
+ elementId: el.id || null,
1167
+ innerText: text,
1168
+ boundingRect: {
1169
+ top: rect.top,
1170
+ left: rect.left,
1171
+ width: rect.width,
1172
+ height: rect.height,
1173
+ },
1174
+ variants: detectClassVariants(el).concat(
1175
+ detectPseudoVariants(el),
1176
+ ),
1177
+ childComponentCount: childCount,
1178
+ })
1179
+ }
1180
+ }
1181
+ index = end
1182
+ if (index < allElements.length) {
1183
+ scheduleNext(processBatch)
1184
+ } else {
1185
+ send({
1186
+ type: 'COMPONENTS_DETECTED',
1187
+ payload: { components: results },
1188
+ })
1189
+ }
1190
+ }
1191
+ if (allElements.length === 0) {
1192
+ send({ type: 'COMPONENTS_DETECTED', payload: { components: [] } })
1193
+ } else {
1194
+ processBatch()
1195
+ }
1196
+ }
1197
+
1198
+ // MutationObserver for DOM changes
1199
+ var mutationPending = false
1200
+ var previousTreeJSON = ''
1201
+ var observer = new MutationObserver(function () {
1202
+ if (mutationPending) return
1203
+ mutationPending = true
1204
+ requestAnimationFrame(function () {
1205
+ mutationPending = false
1206
+ var tree = serializeTree(document.body)
1207
+ if (!tree) return
1208
+ var treeJSON = JSON.stringify(tree)
1209
+ if (treeJSON === previousTreeJSON) return
1210
+ previousTreeJSON = treeJSON
1211
+ var removedSelectors = []
1212
+ if (selectedElement && !document.body.contains(selectedElement)) {
1213
+ removedSelectors.push(generateSelectorPath(selectedElement))
1214
+ clearSelection()
1215
+ }
1216
+ send({
1217
+ type: 'DOM_UPDATED',
1218
+ payload: { tree: tree, removedSelectors: removedSelectors },
1219
+ })
1220
+ })
1221
+ })
1222
+ observer.observe(document.body, {
1223
+ childList: true,
1224
+ subtree: true,
1225
+ attributes: true,
1226
+ attributeFilter: ['class', 'id', 'style'],
1227
+ })
1228
+
1229
+ // --- Drag-and-drop from Add Element palette ---
1230
+ var VOID_TAGS_DND = {
1231
+ img: 1,
1232
+ input: 1,
1233
+ br: 1,
1234
+ hr: 1,
1235
+ area: 1,
1236
+ base: 1,
1237
+ col: 1,
1238
+ embed: 1,
1239
+ link: 1,
1240
+ meta: 1,
1241
+ param: 1,
1242
+ source: 1,
1243
+ track: 1,
1244
+ wbr: 1,
1245
+ }
1246
+ var dropIndicator = document.createElement('div')
1247
+ dropIndicator.style.cssText =
1248
+ 'position:fixed;pointer-events:none;z-index:2147483645;border:2px dashed #4a9eff;background:rgba(74,158,255,0.08);display:none;transition:top 0.08s,left 0.08s,width 0.08s,height 0.08s;'
1249
+ document.body.appendChild(dropIndicator)
1250
+
1251
+ document.addEventListener(
1252
+ 'dragover',
1253
+ function (e) {
1254
+ if (!e.dataTransfer || !e.dataTransfer.types.indexOf) return
1255
+ if (
1256
+ e.dataTransfer.types.indexOf('application/x-dev-editor-element') ===
1257
+ -1
1258
+ )
1259
+ return
1260
+ e.preventDefault()
1261
+ e.dataTransfer.dropEffect = 'copy'
1262
+ var dropTarget = document.elementFromPoint(e.clientX, e.clientY)
1263
+ if (!dropTarget || dropTarget === dropIndicator) return
1264
+ if (dropTarget.id && dropTarget.id.indexOf('dev-editor') === 0) return
1265
+ var rect = dropTarget.getBoundingClientRect()
1266
+ dropIndicator.style.top = rect.top + 'px'
1267
+ dropIndicator.style.left = rect.left + 'px'
1268
+ dropIndicator.style.width = rect.width + 'px'
1269
+ dropIndicator.style.height = rect.height + 'px'
1270
+ dropIndicator.style.display = 'block'
1271
+ },
1272
+ true,
1273
+ )
1274
+
1275
+ document.addEventListener(
1276
+ 'dragleave',
1277
+ function (e) {
1278
+ if (
1279
+ e.relatedTarget === null ||
1280
+ e.relatedTarget === document.documentElement
1281
+ ) {
1282
+ dropIndicator.style.display = 'none'
1283
+ }
1284
+ },
1285
+ true,
1286
+ )
1287
+
1288
+ document.addEventListener(
1289
+ 'drop',
1290
+ function (e) {
1291
+ dropIndicator.style.display = 'none'
1292
+ if (!e.dataTransfer) return
1293
+ var raw = e.dataTransfer.getData('application/x-dev-editor-element')
1294
+ if (!raw) return
1295
+ e.preventDefault()
1296
+ e.stopPropagation()
1297
+ try {
1298
+ var data = JSON.parse(raw)
1299
+ var dropEl = document.elementFromPoint(e.clientX, e.clientY)
1300
+ if (!dropEl) return
1301
+ if (dropEl.id && dropEl.id.indexOf('dev-editor') === 0) return
1302
+ var targetParent = dropEl
1303
+ var insertMode = 'child'
1304
+ if (VOID_TAGS_DND[dropEl.tagName.toLowerCase()]) {
1305
+ targetParent = dropEl.parentElement || document.body
1306
+ insertMode = 'after'
1307
+ }
1308
+ var newEl = document.createElement(data.tag)
1309
+ newEl.setAttribute('data-dev-editor-inserted', 'true')
1310
+ if (data.placeholderText) newEl.textContent = data.placeholderText
1311
+ if (data.defaultStyles) {
1312
+ var dndDS = data.defaultStyles
1313
+ for (var dndDSKey in dndDS) {
1314
+ if (dndDS.hasOwnProperty(dndDSKey))
1315
+ newEl.style.setProperty(dndDSKey, dndDS[dndDSKey])
1316
+ }
1317
+ }
1318
+ if (insertMode === 'after' && dropEl.nextSibling) {
1319
+ targetParent.insertBefore(newEl, dropEl.nextSibling)
1320
+ } else {
1321
+ targetParent.appendChild(newEl)
1322
+ }
1323
+ var newSelector = generateSelectorPath(newEl)
1324
+ var newIndex = Array.from(targetParent.children).indexOf(newEl)
1325
+ send({
1326
+ type: 'ELEMENT_INSERTED',
1327
+ payload: {
1328
+ selectorPath: newSelector,
1329
+ parentSelectorPath: generateSelectorPath(targetParent),
1330
+ tagName: data.tag,
1331
+ insertionIndex: newIndex,
1332
+ placeholderText: data.placeholderText || '',
1333
+ defaultStyles: data.defaultStyles || undefined,
1334
+ },
1335
+ })
1336
+ } catch (err) {}
1337
+ },
1338
+ true,
1339
+ )
1340
+
1341
+ // --- Handle messages from editor ---
1342
+ window.addEventListener('message', function (e) {
1343
+ // Accept messages from detected parentOrigin, or any origin if using wildcard
1344
+ if (parentOrigin !== '*' && e.origin !== parentOrigin) return
1345
+
1346
+ var msg = e.data
1347
+ if (!msg || !msg.type) return
1348
+
1349
+ // Lock in the parent origin from first valid editor message
1350
+ if (parentOrigin === '*' && msg.type) {
1351
+ parentOrigin = e.origin
1352
+ }
1353
+
1354
+ // Mark as connected (stops INSPECTOR_READY retries)
1355
+ if (!connected) {
1356
+ connected = true
1357
+ }
1358
+
1359
+ switch (msg.type) {
1360
+ case 'SELECT_ELEMENT': {
1361
+ try {
1362
+ var el = document.querySelector(msg.payload.selectorPath)
1363
+ if (el) {
1364
+ selectElement(el)
1365
+ el.scrollIntoView({ block: 'center', behavior: 'instant' })
1366
+ }
1367
+ } catch (err) {}
1368
+ break
1369
+ }
1370
+ case 'PREVIEW_CHANGE': {
1371
+ try {
1372
+ var target = document.querySelector(msg.payload.selectorPath)
1373
+ if (target) {
1374
+ var cssProp = camelToKebab(msg.payload.property)
1375
+ requestAnimationFrame(function () {
1376
+ target.style.setProperty(
1377
+ cssProp,
1378
+ msg.payload.value,
1379
+ 'important',
1380
+ )
1381
+ })
1382
+ }
1383
+ } catch (err) {}
1384
+ break
1385
+ }
1386
+ case 'REVERT_CHANGE': {
1387
+ try {
1388
+ var target2 = document.querySelector(msg.payload.selectorPath)
1389
+ if (target2)
1390
+ target2.style.removeProperty(camelToKebab(msg.payload.property))
1391
+ } catch (err) {}
1392
+ break
1393
+ }
1394
+ case 'REVERT_ALL': {
1395
+ var allElements = document.querySelectorAll('[style]')
1396
+ allElements.forEach(function (el) {
1397
+ el.removeAttribute('style')
1398
+ })
1399
+ break
1400
+ }
1401
+ case 'SET_SELECTION_MODE': {
1402
+ selectionModeEnabled = !!msg.payload.enabled
1403
+ if (!selectionModeEnabled) {
1404
+ selectionOverlay.style.display = 'none'
1405
+ hoverOverlay.style.display = 'none'
1406
+ hoveredElement = null
1407
+ selectedElement = null
1408
+ }
1409
+ break
1410
+ }
1411
+ case 'HIDE_HOVER': {
1412
+ hoverOverlay.style.display = 'none'
1413
+ hoveredElement = null
1414
+ break
1415
+ }
1416
+ case 'HIDE_SELECTION_OVERLAY': {
1417
+ selectionOverlay.style.display = 'none'
1418
+ break
1419
+ }
1420
+ case 'SHOW_SELECTION_OVERLAY': {
1421
+ if (selectionModeEnabled && selectedElement) {
1422
+ var sr = selectedElement.getBoundingClientRect()
1423
+ selectionOverlay.style.top = sr.top + 'px'
1424
+ selectionOverlay.style.left = sr.left + 'px'
1425
+ selectionOverlay.style.width = sr.width + 'px'
1426
+ selectionOverlay.style.height = sr.height + 'px'
1427
+ selectionOverlay.style.display = 'block'
1428
+ }
1429
+ break
1430
+ }
1431
+ case 'SET_BREAKPOINT': {
1432
+ break
1433
+ }
1434
+ case 'REQUEST_DOM_TREE': {
1435
+ var tree = serializeTree(document.body)
1436
+ if (tree) send({ type: 'DOM_TREE', payload: { tree: tree } })
1437
+ break
1438
+ }
1439
+ case 'REQUEST_PAGE_LINKS': {
1440
+ var links = []
1441
+ var seen = {}
1442
+ var anchors = document.querySelectorAll('a[href]')
1443
+ for (var ai = 0; ai < anchors.length; ai++) {
1444
+ var rawHref = anchors[ai].getAttribute('href') || ''
1445
+ var linkText = (anchors[ai].textContent || '').trim()
1446
+ var resolved
1447
+ try {
1448
+ resolved = new URL(rawHref, window.location.origin)
1449
+ } catch (e) {
1450
+ continue
1451
+ }
1452
+ if (resolved.origin !== window.location.origin) continue
1453
+ var linkPath = resolved.pathname
1454
+ if (linkPath.indexOf('/api/') === 0 || linkPath === '') continue
1455
+ if (!linkPath.startsWith('/')) continue
1456
+ if (seen[linkPath]) continue
1457
+ seen[linkPath] = true
1458
+ links.push({ href: linkPath, text: linkText })
1459
+ }
1460
+ send({ type: 'PAGE_LINKS', payload: { links: links } })
1461
+ break
1462
+ }
1463
+ case 'REQUEST_CSS_VARIABLES': {
1464
+ var result = scanCSSVariableDefinitions()
1465
+ send({
1466
+ type: 'CSS_VARIABLES',
1467
+ payload: {
1468
+ definitions: result.definitions,
1469
+ isExplicit: result.isExplicit,
1470
+ },
1471
+ })
1472
+ break
1473
+ }
1474
+ case 'REQUEST_COMPONENTS': {
1475
+ try {
1476
+ var compRoot = document.body
1477
+ if (msg.payload && msg.payload.rootSelectorPath) {
1478
+ var rootEl = document.querySelector(msg.payload.rootSelectorPath)
1479
+ if (rootEl) compRoot = rootEl
1480
+ }
1481
+ scanForComponents(compRoot)
1482
+ } catch (err) {}
1483
+ break
1484
+ }
1485
+ case 'APPLY_VARIANT': {
1486
+ try {
1487
+ var avEl = document.querySelector(msg.payload.selectorPath)
1488
+ if (avEl) {
1489
+ if (msg.payload.type === 'class') {
1490
+ if (msg.payload.removeClassNames) {
1491
+ for (
1492
+ var rci = 0;
1493
+ rci < msg.payload.removeClassNames.length;
1494
+ rci++
1495
+ ) {
1496
+ avEl.classList.remove(msg.payload.removeClassNames[rci])
1497
+ }
1498
+ }
1499
+ if (msg.payload.addClassName) {
1500
+ avEl.classList.add(msg.payload.addClassName)
1501
+ }
1502
+ } else if (msg.payload.type === 'pseudo') {
1503
+ if (msg.payload.revertPseudo && msg.payload.pseudoStyles) {
1504
+ var revertKeys = Object.keys(msg.payload.pseudoStyles)
1505
+ for (var rki = 0; rki < revertKeys.length; rki++) {
1506
+ avEl.style.removeProperty(revertKeys[rki])
1507
+ }
1508
+ }
1509
+ if (msg.payload.pseudoStyles && !msg.payload.revertPseudo) {
1510
+ var psKeys = Object.keys(msg.payload.pseudoStyles)
1511
+ for (var psi = 0; psi < psKeys.length; psi++) {
1512
+ avEl.style.setProperty(
1513
+ psKeys[psi],
1514
+ msg.payload.pseudoStyles[psKeys[psi]],
1515
+ 'important',
1516
+ )
1517
+ }
1518
+ }
1519
+ }
1520
+ var avStyles = getComputedStylesForElement(avEl)
1521
+ var avVarUsages = detectCSSVariablesOnElement(avEl)
1522
+ var avRect = avEl.getBoundingClientRect()
1523
+ send({
1524
+ type: 'VARIANT_APPLIED',
1525
+ payload: {
1526
+ selectorPath: msg.payload.selectorPath,
1527
+ computedStyles: avStyles,
1528
+ cssVariableUsages: avVarUsages,
1529
+ boundingRect: {
1530
+ top: avRect.top,
1531
+ left: avRect.left,
1532
+ width: avRect.width,
1533
+ height: avRect.height,
1534
+ },
1535
+ },
1536
+ })
1537
+ }
1538
+ } catch (err) {}
1539
+ break
1540
+ }
1541
+ case 'REVERT_VARIANT': {
1542
+ try {
1543
+ var rvEl = document.querySelector(msg.payload.selectorPath)
1544
+ if (rvEl) {
1545
+ if (msg.payload.removeClassName) {
1546
+ rvEl.classList.remove(msg.payload.removeClassName)
1547
+ }
1548
+ if (msg.payload.restoreClassName) {
1549
+ rvEl.classList.add(msg.payload.restoreClassName)
1550
+ }
1551
+ if (msg.payload.revertPseudo && msg.payload.pseudoProperties) {
1552
+ for (
1553
+ var rppi = 0;
1554
+ rppi < msg.payload.pseudoProperties.length;
1555
+ rppi++
1556
+ ) {
1557
+ rvEl.style.removeProperty(msg.payload.pseudoProperties[rppi])
1558
+ }
1559
+ }
1560
+ }
1561
+ } catch (err) {}
1562
+ break
1563
+ }
1564
+ case 'SET_TEXT_CONTENT': {
1565
+ try {
1566
+ var stEl = document.querySelector(msg.payload.selectorPath)
1567
+ if (stEl) stEl.textContent = msg.payload.text
1568
+ } catch (err) {}
1569
+ break
1570
+ }
1571
+ case 'REVERT_TEXT_CONTENT': {
1572
+ try {
1573
+ var rtEl = document.querySelector(msg.payload.selectorPath)
1574
+ if (rtEl) rtEl.textContent = msg.payload.originalText
1575
+ } catch (err) {}
1576
+ break
1577
+ }
1578
+ case 'DELETE_ELEMENT': {
1579
+ try {
1580
+ var deEl = document.querySelector(msg.payload.selectorPath)
1581
+ if (deEl) {
1582
+ var deComputed = window.getComputedStyle(deEl)
1583
+ var deOrigDisplay = deComputed.getPropertyValue('display')
1584
+ var deSelector = msg.payload.selectorPath
1585
+ var deAttrs = {}
1586
+ for (var deai = 0; deai < deEl.attributes.length; deai++) {
1587
+ var dea = deEl.attributes[deai]
1588
+ deAttrs[dea.name] = dea.value
1589
+ }
1590
+ deEl.style.setProperty('display', 'none', 'important')
1591
+ if (selectedElement === deEl) {
1592
+ selectionOverlay.style.display = 'none'
1593
+ selectedElement = null
1594
+ }
1595
+ send({
1596
+ type: 'ELEMENT_DELETED',
1597
+ payload: {
1598
+ selectorPath: deSelector,
1599
+ originalDisplay: deOrigDisplay,
1600
+ tagName: deEl.tagName.toLowerCase(),
1601
+ className:
1602
+ deEl.className && typeof deEl.className === 'string'
1603
+ ? deEl.className
1604
+ : null,
1605
+ elementId: deEl.id || null,
1606
+ innerText: (deEl.innerText || '').substring(0, 500) || null,
1607
+ attributes: deAttrs,
1608
+ computedStyles: getComputedStylesForElement(deEl),
1609
+ },
1610
+ })
1611
+ }
1612
+ } catch (err) {}
1613
+ break
1614
+ }
1615
+ case 'REVERT_DELETE': {
1616
+ try {
1617
+ var rdEl = document.querySelector(msg.payload.selectorPath)
1618
+ if (rdEl) {
1619
+ rdEl.style.removeProperty('display')
1620
+ }
1621
+ } catch (err) {}
1622
+ break
1623
+ }
1624
+ case 'INSERT_ELEMENT': {
1625
+ try {
1626
+ var ieParent = document.querySelector(
1627
+ msg.payload.parentSelectorPath,
1628
+ )
1629
+ if (ieParent) {
1630
+ var IE_VOID = {
1631
+ img: 1,
1632
+ input: 1,
1633
+ br: 1,
1634
+ hr: 1,
1635
+ area: 1,
1636
+ base: 1,
1637
+ col: 1,
1638
+ embed: 1,
1639
+ link: 1,
1640
+ meta: 1,
1641
+ param: 1,
1642
+ source: 1,
1643
+ track: 1,
1644
+ wbr: 1,
1645
+ }
1646
+ var ieTarget = ieParent
1647
+ var ieMode = 'child'
1648
+ if (IE_VOID[ieParent.tagName.toLowerCase()]) {
1649
+ ieTarget = ieParent.parentElement || document.body
1650
+ ieMode = 'after'
1651
+ }
1652
+ var ieNew = document.createElement(msg.payload.tagName)
1653
+ ieNew.setAttribute('data-dev-editor-inserted', 'true')
1654
+ if (msg.payload.placeholderText)
1655
+ ieNew.textContent = msg.payload.placeholderText
1656
+ if (msg.payload.defaultStyles) {
1657
+ var ieDS = msg.payload.defaultStyles
1658
+ for (var ieDSKey in ieDS) {
1659
+ if (ieDS.hasOwnProperty(ieDSKey))
1660
+ ieNew.style.setProperty(ieDSKey, ieDS[ieDSKey])
1661
+ }
1662
+ }
1663
+ if (ieMode === 'after' && ieParent.nextSibling) {
1664
+ ieTarget.insertBefore(ieNew, ieParent.nextSibling)
1665
+ } else {
1666
+ ieTarget.appendChild(ieNew)
1667
+ }
1668
+ var ieSelector = generateSelectorPath(ieNew)
1669
+ var ieIndex = Array.from(ieTarget.children).indexOf(ieNew)
1670
+ send({
1671
+ type: 'ELEMENT_INSERTED',
1672
+ payload: {
1673
+ selectorPath: ieSelector,
1674
+ parentSelectorPath: generateSelectorPath(ieTarget),
1675
+ tagName: msg.payload.tagName,
1676
+ insertionIndex: ieIndex,
1677
+ placeholderText: msg.payload.placeholderText || '',
1678
+ defaultStyles: msg.payload.defaultStyles || undefined,
1679
+ },
1680
+ })
1681
+ }
1682
+ } catch (err) {}
1683
+ break
1684
+ }
1685
+ case 'REMOVE_INSERTED_ELEMENT': {
1686
+ try {
1687
+ var rieEl = document.querySelector(msg.payload.selectorPath)
1688
+ if (rieEl && rieEl.parentElement) {
1689
+ rieEl.parentElement.removeChild(rieEl)
1690
+ }
1691
+ } catch (err) {}
1692
+ break
1693
+ }
1694
+ case 'MOVE_ELEMENT': {
1695
+ try {
1696
+ var meEl = document.querySelector(msg.payload.selectorPath)
1697
+ var meNewParent = document.querySelector(
1698
+ msg.payload.newParentSelectorPath,
1699
+ )
1700
+ if (
1701
+ meEl &&
1702
+ meNewParent &&
1703
+ meEl !== meNewParent &&
1704
+ !meNewParent.contains(meEl) === false
1705
+ ) {
1706
+ // Allow move even if newParent is a descendant check
1707
+ }
1708
+ if (meEl && meNewParent && meEl !== meNewParent) {
1709
+ // Prevent moving an element into its own descendant
1710
+ if (meEl.contains(meNewParent)) break
1711
+ var meOldParent = meEl.parentElement
1712
+ var meOldIndex = meOldParent
1713
+ ? Array.from(meOldParent.children).indexOf(meEl)
1714
+ : -1
1715
+ var meOldParentSelector = meOldParent
1716
+ ? generateSelectorPath(meOldParent)
1717
+ : ''
1718
+ // Remove from current position
1719
+ if (meOldParent) meOldParent.removeChild(meEl)
1720
+ // Insert at new position
1721
+ var meNewIndex = msg.payload.newIndex
1722
+ var meChildren = meNewParent.children
1723
+ if (meNewIndex >= 0 && meNewIndex < meChildren.length) {
1724
+ meNewParent.insertBefore(meEl, meChildren[meNewIndex])
1725
+ } else {
1726
+ meNewParent.appendChild(meEl)
1727
+ }
1728
+ var meNewSelector = generateSelectorPath(meEl)
1729
+ var meActualIndex = Array.from(meNewParent.children).indexOf(meEl)
1730
+ send({
1731
+ type: 'ELEMENT_MOVED',
1732
+ payload: {
1733
+ selectorPath: msg.payload.selectorPath,
1734
+ newSelectorPath: meNewSelector,
1735
+ oldParentSelectorPath: meOldParentSelector,
1736
+ newParentSelectorPath: msg.payload.newParentSelectorPath,
1737
+ oldIndex: meOldIndex,
1738
+ newIndex: meActualIndex,
1739
+ },
1740
+ })
1741
+ // Update selection overlay if selected element moved
1742
+ if (selectedElement === meEl) {
1743
+ updateSelectionOverlay()
1744
+ }
1745
+ }
1746
+ } catch (err) {}
1747
+ break
1748
+ }
1749
+ case 'REVERT_MOVE_ELEMENT': {
1750
+ try {
1751
+ var rmEl = document.querySelector(msg.payload.selectorPath)
1752
+ var rmOldParent = document.querySelector(
1753
+ msg.payload.oldParentSelectorPath,
1754
+ )
1755
+ if (rmEl && rmOldParent) {
1756
+ var rmCurrentParent = rmEl.parentElement
1757
+ if (rmCurrentParent) rmCurrentParent.removeChild(rmEl)
1758
+ var rmOldIndex = msg.payload.oldIndex
1759
+ var rmChildren = rmOldParent.children
1760
+ if (rmOldIndex >= 0 && rmOldIndex < rmChildren.length) {
1761
+ rmOldParent.insertBefore(rmEl, rmChildren[rmOldIndex])
1762
+ } else {
1763
+ rmOldParent.appendChild(rmEl)
1764
+ }
1765
+ if (selectedElement === rmEl) {
1766
+ updateSelectionOverlay()
1767
+ }
1768
+ }
1769
+ } catch (err) {}
1770
+ break
1771
+ }
1772
+ case 'HEARTBEAT': {
1773
+ send({ type: 'HEARTBEAT_RESPONSE' })
1774
+ break
1775
+ }
1776
+ }
1777
+ })
1778
+
1779
+ // --- SPA Navigation Detection ---
1780
+ var lastPathname = window.location.pathname
1781
+
1782
+ function notifyNavigationIfChanged() {
1783
+ var currentPath = window.location.pathname
1784
+ if (currentPath !== lastPathname) {
1785
+ lastPathname = currentPath
1786
+ send({ type: 'PAGE_NAVIGATE', payload: { path: currentPath } })
1787
+ }
1788
+ }
1789
+
1790
+ window.addEventListener('popstate', notifyNavigationIfChanged)
1791
+
1792
+ if (window.navigation) {
1793
+ window.navigation.addEventListener(
1794
+ 'navigatesuccess',
1795
+ notifyNavigationIfChanged,
1796
+ )
1797
+ }
1798
+
1799
+ // --- Animation reveal ---
1800
+ // Many pages use animation libraries (Framer Motion, GSAP, etc.) that set
1801
+ // elements to opacity:0 + translateY via inline styles as an initial state,
1802
+ // then animate them on scroll or mount. When loaded through the proxy,
1803
+ // these animations may not fire (hydration issues, missing scroll context).
1804
+ // This function detects such hidden elements and forces them visible so the
1805
+ // editor can inspect and edit all content.
1806
+ function revealAnimationHidden() {
1807
+ var revealed = 0
1808
+ var els = document.querySelectorAll('[style]')
1809
+ for (var ri = 0; ri < els.length; ri++) {
1810
+ var rel = els[ri]
1811
+ var rstyle = rel.getAttribute('style') || ''
1812
+ // Only target elements where opacity is explicitly set to 0 via inline style
1813
+ // and optionally have a translateY transform (animation initial state pattern)
1814
+ if (!/opacity\s*:\s*0\b/.test(rstyle)) continue
1815
+ // Skip elements intentionally hidden (display:none, visibility:hidden)
1816
+ var rcs = window.getComputedStyle(rel)
1817
+ if (rcs.display === 'none' || rcs.visibility === 'hidden') continue
1818
+ // Force visible with a smooth transition
1819
+ rel.style.setProperty(
1820
+ 'transition',
1821
+ 'opacity 0.3s ease, transform 0.3s ease',
1822
+ 'important',
1823
+ )
1824
+ rel.style.setProperty('opacity', '1', 'important')
1825
+ // Also clear translateY transforms that are part of the animation initial state
1826
+ if (
1827
+ /translate[YX]\s*\(/.test(rstyle) ||
1828
+ /matrix\s*\(/.test(rcs.transform)
1829
+ ) {
1830
+ rel.style.setProperty('transform', 'none', 'important')
1831
+ }
1832
+ revealed++
1833
+ }
1834
+ return revealed
1835
+ }
1836
+
1837
+ // Run reveal after a delay to give animations a chance to fire naturally.
1838
+ // If they don't fire (proxy context), this catches the stuck elements.
1839
+ // Re-run a few times to catch dynamically rendered content.
1840
+ var revealAttempts = 0
1841
+ var revealTimer = setInterval(function () {
1842
+ revealAnimationHidden()
1843
+ revealAttempts++
1844
+ if (revealAttempts >= 5) clearInterval(revealTimer)
1845
+ }, 800)
1846
+
1847
+ // --- Signal ready with retry ---
1848
+ // Send INSPECTOR_READY immediately, then retry every second until editor responds.
1849
+ // This handles race conditions where the editor isn't listening yet.
1850
+ send({ type: 'INSPECTOR_READY' })
1851
+ var readyRetryCount = 0
1852
+ var readyInterval = setInterval(function () {
1853
+ if (connected) {
1854
+ clearInterval(readyInterval)
1855
+ return
1856
+ }
1857
+ readyRetryCount++
1858
+ if (readyRetryCount >= 30) {
1859
+ clearInterval(readyInterval)
1860
+ return
1861
+ }
1862
+ send({ type: 'INSPECTOR_READY' })
1863
+ }, 1000)
1864
+ }
1865
+
1866
+ // --- Bootstrap: wait for document.body before initializing ---
1867
+ if (document.readyState === 'loading') {
1868
+ document.addEventListener('DOMContentLoaded', initInspector)
1869
+ } else {
1870
+ initInspector()
1871
+ }
1872
+ })()