@treelocator/runtime 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 (227) hide show
  1. package/.eslintrc +3 -0
  2. package/.turbo/turbo-build.log +30 -0
  3. package/.turbo/turbo-test.log +18 -0
  4. package/.turbo/turbo-ts.log +4 -0
  5. package/LICENSE +22 -0
  6. package/babel.config.js +14 -0
  7. package/dist/_generated_styles.d.ts +2 -0
  8. package/dist/_generated_styles.js +1734 -0
  9. package/dist/_generated_tree_icon.d.ts +2 -0
  10. package/dist/_generated_tree_icon.js +2 -0
  11. package/dist/adapters/HtmlElementTreeNode.d.ts +16 -0
  12. package/dist/adapters/HtmlElementTreeNode.js +43 -0
  13. package/dist/adapters/adapterApi.d.ts +30 -0
  14. package/dist/adapters/adapterApi.js +1 -0
  15. package/dist/adapters/createTreeNode.d.ts +2 -0
  16. package/dist/adapters/createTreeNode.js +17 -0
  17. package/dist/adapters/getElementInfo.d.ts +2 -0
  18. package/dist/adapters/getElementInfo.js +19 -0
  19. package/dist/adapters/getParentsPath.d.ts +2 -0
  20. package/dist/adapters/getParentsPath.js +35 -0
  21. package/dist/adapters/getTree.d.ts +1 -0
  22. package/dist/adapters/getTree.js +35 -0
  23. package/dist/adapters/goUpByTheTree.d.ts +7 -0
  24. package/dist/adapters/goUpByTheTree.js +22 -0
  25. package/dist/adapters/jsx/getExpressionData.d.ts +2 -0
  26. package/dist/adapters/jsx/getExpressionData.js +44 -0
  27. package/dist/adapters/jsx/getJSXComponentBoundingBox.d.ts +5 -0
  28. package/dist/adapters/jsx/getJSXComponentBoundingBox.js +46 -0
  29. package/dist/adapters/jsx/jsxAdapter.d.ts +11 -0
  30. package/dist/adapters/jsx/jsxAdapter.js +208 -0
  31. package/dist/adapters/jsx/runtimeStore.d.ts +10 -0
  32. package/dist/adapters/jsx/runtimeStore.js +87 -0
  33. package/dist/adapters/react/fiberToSimple.d.ts +3 -0
  34. package/dist/adapters/react/fiberToSimple.js +55 -0
  35. package/dist/adapters/react/findDebugSource.d.ts +5 -0
  36. package/dist/adapters/react/findDebugSource.js +13 -0
  37. package/dist/adapters/react/findFiberByHtmlElement.d.ts +2 -0
  38. package/dist/adapters/react/findFiberByHtmlElement.js +22 -0
  39. package/dist/adapters/react/gatherFiberRoots.d.ts +2 -0
  40. package/dist/adapters/react/gatherFiberRoots.js +29 -0
  41. package/dist/adapters/react/getAllFiberChildren.d.ts +2 -0
  42. package/dist/adapters/react/getAllFiberChildren.js +9 -0
  43. package/dist/adapters/react/getAllParentsElementsAndRootComponent.d.ts +8 -0
  44. package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +34 -0
  45. package/dist/adapters/react/getAllWrappingParents.d.ts +2 -0
  46. package/dist/adapters/react/getAllWrappingParents.js +19 -0
  47. package/dist/adapters/react/getFiberComponentBoundingBox.d.ts +3 -0
  48. package/dist/adapters/react/getFiberComponentBoundingBox.js +27 -0
  49. package/dist/adapters/react/getFiberLabel.d.ts +3 -0
  50. package/dist/adapters/react/getFiberLabel.js +14 -0
  51. package/dist/adapters/react/getFiberOwnBoundingBox.d.ts +3 -0
  52. package/dist/adapters/react/getFiberOwnBoundingBox.js +6 -0
  53. package/dist/adapters/react/isStyled.d.ts +2 -0
  54. package/dist/adapters/react/isStyled.js +3 -0
  55. package/dist/adapters/react/makeFiberId.d.ts +2 -0
  56. package/dist/adapters/react/makeFiberId.js +16 -0
  57. package/dist/adapters/react/reactAdapter.d.ts +11 -0
  58. package/dist/adapters/react/reactAdapter.js +114 -0
  59. package/dist/adapters/react/searchDevtoolsRenderersForClosestTarget.d.ts +1 -0
  60. package/dist/adapters/react/searchDevtoolsRenderersForClosestTarget.js +11 -0
  61. package/dist/adapters/svelte/svelteAdapter.d.ts +22 -0
  62. package/dist/adapters/svelte/svelteAdapter.js +88 -0
  63. package/dist/adapters/vue/getVNodeBoundingBox.d.ts +4 -0
  64. package/dist/adapters/vue/getVNodeBoundingBox.js +31 -0
  65. package/dist/adapters/vue/vueAdapter.d.ts +15 -0
  66. package/dist/adapters/vue/vueAdapter.js +113 -0
  67. package/dist/browserApi.d.ts +103 -0
  68. package/dist/browserApi.js +160 -0
  69. package/dist/components/Button.d.ts +4 -0
  70. package/dist/components/Button.js +17 -0
  71. package/dist/components/ComponentOutline.d.ts +7 -0
  72. package/dist/components/ComponentOutline.js +93 -0
  73. package/dist/components/MaybeOutline.d.ts +7 -0
  74. package/dist/components/MaybeOutline.js +43 -0
  75. package/dist/components/Outline.d.ts +25 -0
  76. package/dist/components/Outline.js +135 -0
  77. package/dist/components/RenderBoxes.d.ts +4 -0
  78. package/dist/components/RenderBoxes.js +73 -0
  79. package/dist/components/Runtime.d.ts +3 -0
  80. package/dist/components/Runtime.js +188 -0
  81. package/dist/components/SimpleNodeOutline.d.ts +4 -0
  82. package/dist/components/SimpleNodeOutline.js +47 -0
  83. package/dist/components/Toast.d.ts +4 -0
  84. package/dist/components/Toast.js +68 -0
  85. package/dist/components/Tooltip.d.ts +5 -0
  86. package/dist/components/Tooltip.js +21 -0
  87. package/dist/consts.d.ts +6 -0
  88. package/dist/consts.js +5 -0
  89. package/dist/functions/cropPath.d.ts +1 -0
  90. package/dist/functions/cropPath.js +9 -0
  91. package/dist/functions/cropPath.test.d.ts +1 -0
  92. package/dist/functions/cropPath.test.js +16 -0
  93. package/dist/functions/deduplicateLabels.d.ts +2 -0
  94. package/dist/functions/deduplicateLabels.js +12 -0
  95. package/dist/functions/evalTemplate.d.ts +3 -0
  96. package/dist/functions/evalTemplate.js +7 -0
  97. package/dist/functions/evalTemplate.test.d.ts +1 -0
  98. package/dist/functions/evalTemplate.test.js +11 -0
  99. package/dist/functions/findNames.d.ts +5 -0
  100. package/dist/functions/findNames.js +15 -0
  101. package/dist/functions/formatAncestryChain.d.ts +9 -0
  102. package/dist/functions/formatAncestryChain.js +56 -0
  103. package/dist/functions/getBoundingRect.d.ts +1 -0
  104. package/dist/functions/getBoundingRect.js +11 -0
  105. package/dist/functions/getComposedBoundingBox.d.ts +2 -0
  106. package/dist/functions/getComposedBoundingBox.js +20 -0
  107. package/dist/functions/getIdsOnPathToRoot.d.ts +3 -0
  108. package/dist/functions/getIdsOnPathToRoot.js +15 -0
  109. package/dist/functions/getMultipleElementsBoundingBox.d.ts +2 -0
  110. package/dist/functions/getMultipleElementsBoundingBox.js +20 -0
  111. package/dist/functions/getPathToParent.d.ts +1 -0
  112. package/dist/functions/getPathToParent.js +15 -0
  113. package/dist/functions/getReferenceId.d.ts +1 -0
  114. package/dist/functions/getReferenceId.js +9 -0
  115. package/dist/functions/getUsableFileName.d.ts +1 -0
  116. package/dist/functions/getUsableFileName.js +17 -0
  117. package/dist/functions/getUsableFileName.test.d.ts +1 -0
  118. package/dist/functions/getUsableFileName.test.js +16 -0
  119. package/dist/functions/getUsableName.d.ts +2 -0
  120. package/dist/functions/getUsableName.js +47 -0
  121. package/dist/functions/isCombinationModifiersPressed.d.ts +4 -0
  122. package/dist/functions/isCombinationModifiersPressed.js +16 -0
  123. package/dist/functions/isLocatorsOwnElement.d.ts +1 -0
  124. package/dist/functions/isLocatorsOwnElement.js +3 -0
  125. package/dist/functions/mergeRects.d.ts +2 -0
  126. package/dist/functions/mergeRects.js +10 -0
  127. package/dist/functions/mergeRects.test.d.ts +1 -0
  128. package/dist/functions/mergeRects.test.js +23 -0
  129. package/dist/functions/nonNullable.d.ts +1 -0
  130. package/dist/functions/nonNullable.js +3 -0
  131. package/dist/functions/parseDataId.d.ts +3 -0
  132. package/dist/functions/parseDataId.js +44 -0
  133. package/dist/functions/transformPath.d.ts +1 -0
  134. package/dist/functions/transformPath.js +7 -0
  135. package/dist/functions/transformPath.test.d.ts +1 -0
  136. package/dist/functions/transformPath.test.js +16 -0
  137. package/dist/global.d.js +1 -0
  138. package/dist/index.d.ts +11 -0
  139. package/dist/index.js +13 -0
  140. package/dist/initRuntime.d.ts +8 -0
  141. package/dist/initRuntime.js +80 -0
  142. package/dist/output.css +1733 -0
  143. package/dist/types/LabelData.d.ts +5 -0
  144. package/dist/types/LabelData.js +1 -0
  145. package/dist/types/TreeNode.d.ts +19 -0
  146. package/dist/types/TreeNode.js +1 -0
  147. package/dist/types/types.d.ts +53 -0
  148. package/dist/types/types.js +1 -0
  149. package/jest.config.ts +195 -0
  150. package/package.json +75 -0
  151. package/scripts/wrapCSS.js +26 -0
  152. package/scripts/wrapImage.js +24 -0
  153. package/src/_generated_styles.ts +1734 -0
  154. package/src/_generated_tree_icon.ts +2 -0
  155. package/src/adapters/HtmlElementTreeNode.ts +51 -0
  156. package/src/adapters/adapterApi.ts +35 -0
  157. package/src/adapters/createTreeNode.ts +25 -0
  158. package/src/adapters/getElementInfo.tsx +27 -0
  159. package/src/adapters/getParentsPath.tsx +49 -0
  160. package/src/adapters/getTree.tsx +45 -0
  161. package/src/adapters/goUpByTheTree.ts +20 -0
  162. package/src/adapters/jsx/getExpressionData.ts +47 -0
  163. package/src/adapters/jsx/getJSXComponentBoundingBox.ts +63 -0
  164. package/src/adapters/jsx/jsxAdapter.ts +276 -0
  165. package/src/adapters/jsx/runtimeStore.ts +94 -0
  166. package/src/adapters/react/fiberToSimple.tsx +72 -0
  167. package/src/adapters/react/findDebugSource.ts +15 -0
  168. package/src/adapters/react/findFiberByHtmlElement.ts +27 -0
  169. package/src/adapters/react/gatherFiberRoots.tsx +36 -0
  170. package/src/adapters/react/getAllFiberChildren.tsx +11 -0
  171. package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +52 -0
  172. package/src/adapters/react/getAllWrappingParents.ts +25 -0
  173. package/src/adapters/react/getFiberComponentBoundingBox.ts +30 -0
  174. package/src/adapters/react/getFiberLabel.ts +20 -0
  175. package/src/adapters/react/getFiberOwnBoundingBox.ts +9 -0
  176. package/src/adapters/react/isStyled.ts +5 -0
  177. package/src/adapters/react/makeFiberId.tsx +19 -0
  178. package/src/adapters/react/reactAdapter.ts +148 -0
  179. package/src/adapters/react/searchDevtoolsRenderersForClosestTarget.ts +15 -0
  180. package/src/adapters/svelte/svelteAdapter.ts +111 -0
  181. package/src/adapters/vue/getVNodeBoundingBox.tsx +42 -0
  182. package/src/adapters/vue/vueAdapter.ts +139 -0
  183. package/src/assets/tree-icon.png +0 -0
  184. package/src/browserApi.ts +288 -0
  185. package/src/components/Button.tsx +14 -0
  186. package/src/components/ComponentOutline.tsx +98 -0
  187. package/src/components/MaybeOutline.tsx +49 -0
  188. package/src/components/Outline.tsx +153 -0
  189. package/src/components/RenderBoxes.tsx +57 -0
  190. package/src/components/Runtime.tsx +246 -0
  191. package/src/components/SimpleNodeOutline.tsx +27 -0
  192. package/src/components/Toast.tsx +83 -0
  193. package/src/components/Tooltip.tsx +28 -0
  194. package/src/consts.ts +7 -0
  195. package/src/functions/cropPath.test.ts +18 -0
  196. package/src/functions/cropPath.ts +12 -0
  197. package/src/functions/deduplicateLabels.ts +16 -0
  198. package/src/functions/evalTemplate.test.ts +12 -0
  199. package/src/functions/evalTemplate.ts +8 -0
  200. package/src/functions/findNames.ts +20 -0
  201. package/src/functions/formatAncestryChain.ts +80 -0
  202. package/src/functions/getBoundingRect.tsx +11 -0
  203. package/src/functions/getComposedBoundingBox.tsx +25 -0
  204. package/src/functions/getIdsOnPathToRoot.tsx +21 -0
  205. package/src/functions/getMultipleElementsBoundingBox.tsx +25 -0
  206. package/src/functions/getPathToParent.tsx +17 -0
  207. package/src/functions/getReferenceId.tsx +10 -0
  208. package/src/functions/getUsableFileName.test.tsx +24 -0
  209. package/src/functions/getUsableFileName.tsx +19 -0
  210. package/src/functions/getUsableName.ts +52 -0
  211. package/src/functions/isCombinationModifiersPressed.ts +32 -0
  212. package/src/functions/isLocatorsOwnElement.tsx +9 -0
  213. package/src/functions/mergeRects.test.ts +15 -0
  214. package/src/functions/mergeRects.tsx +12 -0
  215. package/src/functions/nonNullable.ts +3 -0
  216. package/src/functions/parseDataId.ts +62 -0
  217. package/src/functions/transformPath.test.ts +28 -0
  218. package/src/functions/transformPath.ts +7 -0
  219. package/src/global.d.ts +31 -0
  220. package/src/index.ts +18 -0
  221. package/src/initRuntime.ts +83 -0
  222. package/src/main.css +3 -0
  223. package/src/types/LabelData.ts +6 -0
  224. package/src/types/TreeNode.ts +22 -0
  225. package/src/types/types.ts +55 -0
  226. package/tailwind.config.js +9 -0
  227. package/tsconfig.json +20 -0
@@ -0,0 +1,288 @@
1
+ import { AdapterId } from "./consts";
2
+ import { createTreeNode } from "./adapters/createTreeNode";
3
+ import {
4
+ collectAncestry,
5
+ formatAncestryChain,
6
+ AncestryItem,
7
+ } from "./functions/formatAncestryChain";
8
+
9
+ export interface LocatorJSAPI {
10
+ /**
11
+ * Get formatted ancestry path for an element.
12
+ * Returns a human-readable string showing the component hierarchy from root to target element.
13
+ *
14
+ * @param elementOrSelector - HTMLElement or CSS selector string (e.g., 'button.submit', '#login-form')
15
+ * @returns Formatted ancestry chain as string, or null if element not found/unsupported
16
+ *
17
+ * @example
18
+ * // Basic usage with CSS selector
19
+ * window.__locatorjs__.getPath('button.submit');
20
+ * // Returns:
21
+ * // "div in App at src/App.tsx:15
22
+ * // └─ button in SubmitButton at src/components/SubmitButton.tsx:12"
23
+ *
24
+ * @example
25
+ * // Usage with HTMLElement
26
+ * const button = document.querySelector('button.submit');
27
+ * window.__locatorjs__.getPath(button);
28
+ *
29
+ * @example
30
+ * // In Playwright
31
+ * const path = await page.evaluate(() => {
32
+ * return window.__locatorjs__.getPath('button.submit');
33
+ * });
34
+ * console.log(path);
35
+ */
36
+ getPath(elementOrSelector: HTMLElement | string): string | null;
37
+
38
+ /**
39
+ * Get raw ancestry data for an element.
40
+ * Returns an array of objects containing component names, file paths, and line numbers.
41
+ *
42
+ * @param elementOrSelector - HTMLElement or CSS selector string
43
+ * @returns Array of ancestry items with structure:
44
+ * - elementName: HTML element tag (e.g., 'div', 'button')
45
+ * - componentName: Component name (e.g., 'LoginButton')
46
+ * - filePath: Source file path (e.g., 'src/components/LoginButton.tsx')
47
+ * - line: Line number in source file
48
+ *
49
+ * @example
50
+ * // Get structured ancestry data
51
+ * const ancestry = window.__locatorjs__.getAncestry('button.submit');
52
+ * // Returns: [
53
+ * // { elementName: 'div', componentName: 'App', filePath: 'src/App.tsx', line: 15 },
54
+ * // { elementName: 'button', componentName: 'SubmitButton', filePath: 'src/components/SubmitButton.tsx', line: 12 }
55
+ * // ]
56
+ *
57
+ * @example
58
+ * // In Playwright - extract just component names
59
+ * const components = await page.evaluate(() => {
60
+ * const ancestry = window.__locatorjs__.getAncestry('.my-element');
61
+ * return ancestry?.map(item => item.componentName).filter(Boolean);
62
+ * });
63
+ */
64
+ getAncestry(elementOrSelector: HTMLElement | string): AncestryItem[] | null;
65
+
66
+ /**
67
+ * Get both formatted path and raw ancestry data in a single call.
68
+ * Convenience method that combines getPath() and getAncestry().
69
+ *
70
+ * @param elementOrSelector - HTMLElement or CSS selector string
71
+ * @returns Object with { path: string, ancestry: AncestryItem[] }, or null
72
+ *
73
+ * @example
74
+ * // Get both formats at once
75
+ * const data = window.__locatorjs__.getPathData('button.submit');
76
+ * console.log(data.path); // Human-readable string
77
+ * console.log(data.ancestry); // Structured array
78
+ *
79
+ * @example
80
+ * // In Playwright - useful for comprehensive debugging
81
+ * const data = await page.evaluate(() => {
82
+ * return window.__locatorjs__.getPathData('.error-message');
83
+ * });
84
+ * if (data) {
85
+ * console.log('Component tree:', data.path);
86
+ * console.log('Source files:', data.ancestry.map(a => a.filePath));
87
+ * }
88
+ */
89
+ getPathData(
90
+ elementOrSelector: HTMLElement | string
91
+ ): { path: string; ancestry: AncestryItem[] } | null;
92
+
93
+ /**
94
+ * Display help information about the LocatorJS API.
95
+ * Shows usage examples and method descriptions for browser automation tools.
96
+ *
97
+ * @returns Help text as a string
98
+ *
99
+ * @example
100
+ * // View help in browser console
101
+ * console.log(window.__locatorjs__.help());
102
+ *
103
+ * @example
104
+ * // In Playwright - view help
105
+ * const help = await page.evaluate(() => window.__locatorjs__.help());
106
+ * console.log(help);
107
+ */
108
+ help(): string;
109
+ }
110
+
111
+ let adapterId: AdapterId | undefined;
112
+
113
+ function resolveElement(
114
+ elementOrSelector: HTMLElement | string
115
+ ): HTMLElement | null {
116
+ if (typeof elementOrSelector === "string") {
117
+ const element = document.querySelector(elementOrSelector);
118
+ return element instanceof HTMLElement ? element : null;
119
+ }
120
+ return elementOrSelector;
121
+ }
122
+
123
+ function getAncestryForElement(element: HTMLElement): AncestryItem[] | null {
124
+ const treeNode = createTreeNode(element, adapterId);
125
+ if (!treeNode) {
126
+ return null;
127
+ }
128
+ return collectAncestry(treeNode);
129
+ }
130
+
131
+ const HELP_TEXT = `
132
+ ╔═══════════════════════════════════════════════════════════════════════════╗
133
+ ║ LocatorJS Browser API ║
134
+ ║ Programmatic Component Ancestry Access ║
135
+ ╚═══════════════════════════════════════════════════════════════════════════╝
136
+
137
+ METHODS:
138
+ --------
139
+
140
+ 1. getPath(elementOrSelector)
141
+ Returns a formatted string showing the component hierarchy.
142
+
143
+ Usage:
144
+ window.__locatorjs__.getPath('button.submit')
145
+ window.__locatorjs__.getPath(document.querySelector('.my-button'))
146
+
147
+ Returns:
148
+ "div in App at src/App.tsx:15
149
+ └─ button in SubmitButton at src/components/SubmitButton.tsx:12"
150
+
151
+ 2. getAncestry(elementOrSelector)
152
+ Returns raw ancestry data as an array of objects.
153
+
154
+ Usage:
155
+ window.__locatorjs__.getAncestry('button.submit')
156
+
157
+ Returns:
158
+ [
159
+ { elementName: 'div', componentName: 'App',
160
+ filePath: 'src/App.tsx', line: 15 },
161
+ { elementName: 'button', componentName: 'SubmitButton',
162
+ filePath: 'src/components/SubmitButton.tsx', line: 12 }
163
+ ]
164
+
165
+ 3. getPathData(elementOrSelector)
166
+ Returns both formatted path and raw ancestry in one call.
167
+
168
+ Usage:
169
+ const data = window.__locatorjs__.getPathData('button.submit')
170
+ console.log(data.path) // formatted string
171
+ console.log(data.ancestry) // structured array
172
+
173
+ 4. help()
174
+ Displays this help message.
175
+
176
+ PLAYWRIGHT EXAMPLES:
177
+ -------------------
178
+
179
+ // Get component path for debugging
180
+ const path = await page.evaluate(() => {
181
+ return window.__locatorjs__.getPath('button.submit');
182
+ });
183
+ console.log(path);
184
+
185
+ // Extract component names
186
+ const components = await page.evaluate(() => {
187
+ const ancestry = window.__locatorjs__.getAncestry('.error-message');
188
+ return ancestry?.map(item => item.componentName).filter(Boolean);
189
+ });
190
+
191
+ // Create a test helper
192
+ async function getComponentPath(page, selector) {
193
+ return await page.evaluate((sel) => {
194
+ return window.__locatorjs__.getPath(sel);
195
+ }, selector);
196
+ }
197
+
198
+ PUPPETEER EXAMPLES:
199
+ ------------------
200
+
201
+ const path = await page.evaluate(() => {
202
+ return window.__locatorjs__.getPath('.my-button');
203
+ });
204
+
205
+ SELENIUM EXAMPLES:
206
+ -----------------
207
+
208
+ const path = await driver.executeScript(() => {
209
+ return window.__locatorjs__.getPath('button.submit');
210
+ });
211
+
212
+ CYPRESS EXAMPLES:
213
+ ----------------
214
+
215
+ cy.window().then((win) => {
216
+ const path = win.__locatorjs__.getPath('button.submit');
217
+ cy.log(path);
218
+ });
219
+
220
+ NOTES:
221
+ ------
222
+ • Accepts CSS selectors or HTMLElement objects
223
+ • Returns null if element not found or framework not supported
224
+ • Works with React, Vue, Svelte, Preact, and any JSX framework
225
+ • Automatically installed when LocatorJS runtime initializes
226
+
227
+ Documentation: https://github.com/infi-pc/locatorjs
228
+ `;
229
+
230
+ export function createBrowserAPI(
231
+ adapterIdParam?: AdapterId
232
+ ): LocatorJSAPI {
233
+ adapterId = adapterIdParam;
234
+
235
+ return {
236
+ getPath(elementOrSelector: HTMLElement | string): string | null {
237
+ const element = resolveElement(elementOrSelector);
238
+ if (!element) {
239
+ return null;
240
+ }
241
+
242
+ const ancestry = getAncestryForElement(element);
243
+ if (!ancestry) {
244
+ return null;
245
+ }
246
+
247
+ return formatAncestryChain(ancestry);
248
+ },
249
+
250
+ getAncestry(elementOrSelector: HTMLElement | string): AncestryItem[] | null {
251
+ const element = resolveElement(elementOrSelector);
252
+ if (!element) {
253
+ return null;
254
+ }
255
+
256
+ return getAncestryForElement(element);
257
+ },
258
+
259
+ getPathData(
260
+ elementOrSelector: HTMLElement | string
261
+ ): { path: string; ancestry: AncestryItem[] } | null {
262
+ const element = resolveElement(elementOrSelector);
263
+ if (!element) {
264
+ return null;
265
+ }
266
+
267
+ const ancestry = getAncestryForElement(element);
268
+ if (!ancestry) {
269
+ return null;
270
+ }
271
+
272
+ return {
273
+ path: formatAncestryChain(ancestry),
274
+ ancestry,
275
+ };
276
+ },
277
+
278
+ help(): string {
279
+ return HELP_TEXT;
280
+ },
281
+ };
282
+ }
283
+
284
+ export function installBrowserAPI(adapterIdParam?: AdapterId): void {
285
+ if (typeof window !== "undefined") {
286
+ (window as any).__locatorjs__ = createBrowserAPI(adapterIdParam);
287
+ }
288
+ }
@@ -0,0 +1,14 @@
1
+ export function Button(props: { onClick: () => void; children: any }) {
2
+ return (
3
+ <button
4
+ class="py-1 px-1 hover:bg-white/30 pointer hover:text-gray-100 rounded"
5
+ onClick={(e) => {
6
+ e.preventDefault();
7
+ e.stopPropagation();
8
+ props.onClick();
9
+ }}
10
+ >
11
+ {props.children}
12
+ </button>
13
+ );
14
+ }
@@ -0,0 +1,98 @@
1
+ import { For } from "solid-js";
2
+ import { PADDING } from "../consts";
3
+ import { LabelData } from "../types/LabelData";
4
+ import { SimpleDOMRect } from "../types/types";
5
+
6
+ export function ComponentOutline(props: {
7
+ bbox: SimpleDOMRect;
8
+ labels: LabelData[];
9
+ element: HTMLElement;
10
+ }) {
11
+ const isInside = () => props.bbox.height >= window.innerHeight - 40;
12
+ const isBelow = () => props.bbox.y < 30 && !isInside();
13
+
14
+ const left = () => Math.max(props.bbox.x - PADDING, 0);
15
+ const top = () => Math.max(props.bbox.y - PADDING, 0);
16
+
17
+ const cutFromTop = () => (props.bbox.y < 0 ? -(props.bbox.y - PADDING) : 0);
18
+ const cutFromLeft = () => (props.bbox.x < 0 ? -(props.bbox.x - PADDING) : 0);
19
+
20
+ const width = () =>
21
+ Math.min(props.bbox.width - cutFromLeft() + PADDING * 2, window.innerWidth);
22
+ const height = () =>
23
+ Math.min(
24
+ props.bbox.height - cutFromTop() + PADDING * 2,
25
+ window.innerHeight
26
+ );
27
+
28
+ return (
29
+ <div
30
+ class="border border-purple-500"
31
+ style={{
32
+ "z-index": 1,
33
+ position: "fixed",
34
+ left: left() + "px",
35
+ top: top() + "px",
36
+ width: width() + "px",
37
+ height: height() + "px",
38
+ "border-top-left-radius": left() === 0 || top() === 0 ? "0" : "8px",
39
+ "border-top-right-radius":
40
+ left() + width() === window.innerWidth || top() === 0 ? "0" : "8px",
41
+ "border-bottom-left-radius":
42
+ left() === 0 || top() + height() === window.innerHeight ? "0" : "8px",
43
+ "border-bottom-right-radius":
44
+ left() + width() === window.innerWidth ||
45
+ top() + height() === window.innerHeight
46
+ ? "0"
47
+ : "8px",
48
+ }}
49
+ >
50
+ <div
51
+ id="locatorjs-labels-section"
52
+ style={{
53
+ position: "absolute",
54
+ display: "flex",
55
+ "justify-content": "center",
56
+ bottom: isBelow() ? (isInside() ? "2px" : "-28px") : undefined,
57
+ top: isBelow() ? undefined : isInside() ? "2px" : "-28px",
58
+ left: "0px",
59
+ width: "100%",
60
+ "pointer-events": "auto",
61
+ cursor: "pointer",
62
+ ...(isBelow()
63
+ ? {
64
+ "border-bottom-left-radius": "100%",
65
+ "border-bottom-right-radius": "100%",
66
+ }
67
+ : {
68
+ "border-top-left-radius": "100%",
69
+ "border-top-right-radius": "100%",
70
+ }),
71
+ }}
72
+ >
73
+ <div
74
+ id="locatorjs-labels-wrapper"
75
+ style={{
76
+ padding: isBelow() ? "10px 10px 2px 10px" : "2px 10px 10px 10px",
77
+ }}
78
+ >
79
+ <For each={props.labels}>
80
+ {(label) => {
81
+ const labelClass =
82
+ "bg-purple-500 block text-white text-xs font-bold text-center px-1 py-0.5 rounded whitespace-nowrap pointer-events-auto";
83
+ const labelStyles = {
84
+ "line-height": "18px",
85
+ };
86
+
87
+ return (
88
+ <div class={labelClass} style={labelStyles}>
89
+ {label.label}
90
+ </div>
91
+ );
92
+ }}
93
+ </For>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,49 @@
1
+ import { Targets } from "@locator/shared";
2
+ import { createMemo } from "solid-js";
3
+ import { AdapterId } from "../consts";
4
+ import { getElementInfo } from "../adapters/getElementInfo";
5
+ import { Outline } from "./Outline";
6
+
7
+ export function MaybeOutline(props: {
8
+ currentElement: HTMLElement;
9
+ adapterId?: AdapterId;
10
+ targets: Targets;
11
+ }) {
12
+ const elInfo = createMemo(() =>
13
+ getElementInfo(props.currentElement, props.adapterId)
14
+ );
15
+ const box = () => props.currentElement.getBoundingClientRect();
16
+ return (
17
+ <>
18
+ {elInfo() ? (
19
+ <Outline
20
+ element={elInfo()!}
21
+ targets={props.targets}
22
+ />
23
+ ) : (
24
+ <div class="fixed top-0 left-0 w-screen h-screen flex items-center justify-center">
25
+ <div
26
+ class="flex items-center justify-center"
27
+ style={{
28
+ position: "absolute",
29
+ left: box().x + "px",
30
+ top: box().y + "px",
31
+ width: box().width + "px",
32
+ height: box().height + "px",
33
+ "background-color": "rgba(222, 0, 0, 0.3)",
34
+ border: "1px solid rgba(222, 0, 0, 0.5)",
35
+ "border-radius": "2px",
36
+ "font-size": "12px",
37
+ "font-weight": "bold",
38
+ "text-shadow":
39
+ "-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
40
+ "text-overflow": "ellipsis",
41
+ }}
42
+ >
43
+ No source found
44
+ </div>
45
+ </div>
46
+ )}
47
+ </>
48
+ );
49
+ }
@@ -0,0 +1,153 @@
1
+ import type { Targets } from "@locator/shared";
2
+ import type { FullElementInfo } from "../adapters/adapterApi";
3
+ import { RenderBoxes } from "./RenderBoxes";
4
+
5
+ type Box = {
6
+ top: number;
7
+ left: number;
8
+ width: number;
9
+ height: number;
10
+ label: string;
11
+ };
12
+ type IndividualBoxes = {
13
+ top: Box;
14
+ left: Box;
15
+ right: Box;
16
+ bottom: Box;
17
+ };
18
+
19
+ export type AllBoxes = {
20
+ margin: IndividualBoxes;
21
+ padding: IndividualBoxes;
22
+ innerBox: Box;
23
+ };
24
+
25
+ export function Outline(props: {
26
+ element: FullElementInfo;
27
+ targets: Targets;
28
+ }) {
29
+ const box = () => props.element.thisElement.box;
30
+
31
+ const domElementInfo = () => {
32
+ const htmlElement = props.element.htmlElement;
33
+ const box = props.element.thisElement.box;
34
+ if (htmlElement && box) {
35
+ const style = window.getComputedStyle(htmlElement);
36
+
37
+ const margin = {
38
+ top: parseFloat(style.marginTop),
39
+ left: parseFloat(style.marginLeft),
40
+ right: parseFloat(style.marginRight),
41
+ bottom: parseFloat(style.marginBottom),
42
+ };
43
+ const padding = {
44
+ top: parseFloat(style.paddingTop),
45
+ left: parseFloat(style.paddingLeft),
46
+ right: parseFloat(style.paddingRight),
47
+ bottom: parseFloat(style.paddingBottom),
48
+ };
49
+ const individualMarginBoxes: IndividualBoxes = {
50
+ top: {
51
+ top: box.y - margin.top,
52
+ left: box.x,
53
+ width: box.width,
54
+ height: margin.top,
55
+ label: label(margin.top),
56
+ },
57
+ left: {
58
+ top: box.y - margin.top,
59
+ left: box.x - margin.left,
60
+ width: margin.left,
61
+ height: box.height + margin.top + margin.bottom,
62
+ label: label(margin.left),
63
+ },
64
+ right: {
65
+ top: box.y - margin.top,
66
+ left: box.x + box.width,
67
+ width: margin.right,
68
+ height: box.height + margin.top + margin.bottom,
69
+ label: label(margin.right),
70
+ },
71
+ bottom: {
72
+ top: box.y + box.height,
73
+ left: box.x,
74
+ width: box.width,
75
+ height: margin.bottom,
76
+ label: label(margin.bottom),
77
+ },
78
+ };
79
+
80
+ const individualPaddingBoxes: IndividualBoxes = {
81
+ top: {
82
+ top: box.y,
83
+ left: box.x,
84
+ width: box.width,
85
+ height: padding.top,
86
+ label: label(padding.top),
87
+ },
88
+ left: {
89
+ top: box.y + padding.top,
90
+ left: box.x,
91
+ width: padding.left,
92
+ height: box.height - padding.top - padding.bottom,
93
+ label: label(padding.left),
94
+ },
95
+ right: {
96
+ top: box.y + padding.top,
97
+ left: box.x + box.width - padding.right,
98
+ width: padding.right,
99
+ height: box.height - padding.top - padding.bottom,
100
+ label: label(padding.right),
101
+ },
102
+ bottom: {
103
+ top: box.y + box.height - padding.bottom,
104
+ left: box.x,
105
+ width: box.width,
106
+ height: padding.bottom,
107
+ label: label(padding.bottom),
108
+ },
109
+ };
110
+
111
+ return {
112
+ margin: individualMarginBoxes,
113
+ padding: individualPaddingBoxes,
114
+ innerBox: {
115
+ top: box.y + padding.top,
116
+ left: box.x + padding.left,
117
+ width: box.width - padding.left - padding.right,
118
+ height: box.height - padding.top - padding.bottom,
119
+ label: "",
120
+ },
121
+ };
122
+ }
123
+
124
+ return null;
125
+ };
126
+
127
+ return (
128
+ <>
129
+ <div>
130
+ {domElementInfo() && <RenderBoxes allBoxes={domElementInfo()!} />}
131
+ <div
132
+ class="fixed flex text-xs font-bold items-center justify-center text-sky-500 rounded border border-solid border-sky-500"
133
+ style={{
134
+ "z-index": 2,
135
+ left: box().x + "px",
136
+ top: box().y + "px",
137
+ width: box().width + "px",
138
+ height: box().height + "px",
139
+ "text-shadow":
140
+ "-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
141
+ "text-overflow": "ellipsis",
142
+ }}
143
+ >
144
+ {props.element.thisElement.label}
145
+ </div>
146
+ </div>
147
+ </>
148
+ );
149
+ }
150
+
151
+ function label(value: number) {
152
+ return value ? `${value}px` : "";
153
+ }
@@ -0,0 +1,57 @@
1
+ import { AllBoxes } from "./Outline";
2
+
3
+ export function RenderBoxes(props: { allBoxes: AllBoxes }) {
4
+ return (
5
+ <>
6
+ {Object.entries(props.allBoxes.margin).map(([, box]) => {
7
+ return (
8
+ <div
9
+ class="fixed flex text-xs font-bold items-center justify-center text-orange-500 bg-orange-500/30"
10
+ style={{
11
+ left: box.left + "px",
12
+ top: box.top + "px",
13
+ width: box.width + "px",
14
+ height: box.height + "px",
15
+ "text-shadow":
16
+ "-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
17
+ }}
18
+ >
19
+ {/* {box.label} */}
20
+ </div>
21
+ );
22
+ })}
23
+ {Object.entries(props.allBoxes.padding).map(([, box]) => {
24
+ return (
25
+ <div
26
+ class="fixed flex text-xs font-bold items-center justify-center text-green-500 bg-green-500/30"
27
+ style={{
28
+ left: box.left + "px",
29
+ top: box.top + "px",
30
+ width: box.width + "px",
31
+ height: box.height + "px",
32
+ "text-shadow":
33
+ "-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
34
+ }}
35
+ >
36
+ {/* {box.label} */}
37
+ </div>
38
+ );
39
+ })}
40
+
41
+ <div
42
+ class="fixed flex text-xs font-bold items-center justify-center text-blue-500 bg-blue-500/30"
43
+ style={{
44
+ left: props.allBoxes.innerBox.left + "px",
45
+ top: props.allBoxes.innerBox.top + "px",
46
+ width: props.allBoxes.innerBox.width + "px",
47
+ height: props.allBoxes.innerBox.height + "px",
48
+
49
+ "text-shadow":
50
+ "-1px 1px 0 #fff, 1px 1px 0 #fff, 1px -1px 0 #fff, -1px -1px 0 #fff",
51
+ }}
52
+ >
53
+ {props.allBoxes.innerBox.label}
54
+ </div>
55
+ </>
56
+ );
57
+ }