@treelocator/runtime 0.5.2 → 0.6.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 (163) hide show
  1. package/.eslintignore +1 -0
  2. package/dist/_generated_styles.d.ts +1 -1
  3. package/dist/_generated_styles.js +20 -0
  4. package/dist/_generated_tree_icon.d.ts +1 -1
  5. package/dist/adapters/HtmlElementTreeNode.d.ts +2 -2
  6. package/dist/adapters/HtmlElementTreeNode.js +4 -6
  7. package/dist/adapters/createTreeNode.js +17 -44
  8. package/dist/adapters/detectFramework.d.ts +8 -0
  9. package/dist/adapters/detectFramework.js +25 -0
  10. package/dist/adapters/detectFramework.test.d.ts +1 -0
  11. package/dist/adapters/detectFramework.test.js +60 -0
  12. package/dist/adapters/jsx/jsxAdapter.js +54 -89
  13. package/dist/adapters/jsx/jsxAdapter.test.d.ts +1 -0
  14. package/dist/adapters/jsx/jsxAdapter.test.js +273 -0
  15. package/dist/adapters/nextjs/parseNextjsDataAttributes.js +1 -1
  16. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.d.ts +1 -0
  17. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.js +158 -0
  18. package/dist/adapters/react/findFiberByHtmlElement.d.ts +1 -1
  19. package/dist/adapters/react/findFiberByHtmlElement.js +1 -1
  20. package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +4 -0
  21. package/dist/adapters/resolveAdapter.d.ts +1 -1
  22. package/dist/adapters/resolveAdapter.js +4 -8
  23. package/dist/adapters/svelte/svelteAdapter.test.d.ts +1 -0
  24. package/dist/adapters/svelte/svelteAdapter.test.js +280 -0
  25. package/dist/adapters/vue/vueAdapter.test.d.ts +1 -0
  26. package/dist/adapters/vue/vueAdapter.test.js +222 -0
  27. package/dist/browserApi.d.ts +148 -0
  28. package/dist/browserApi.js +146 -5
  29. package/dist/browserApi.test.d.ts +1 -0
  30. package/dist/browserApi.test.js +287 -0
  31. package/dist/components/RecordingPillButton.d.ts +11 -0
  32. package/dist/components/RecordingPillButton.js +202 -0
  33. package/dist/components/RecordingResults.d.ts +2 -0
  34. package/dist/components/RecordingResults.js +213 -78
  35. package/dist/components/Runtime.js +161 -554
  36. package/dist/components/SettingsPanel.d.ts +5 -0
  37. package/dist/components/SettingsPanel.js +312 -0
  38. package/dist/consoleCapture.d.ts +9 -0
  39. package/dist/consoleCapture.js +95 -0
  40. package/dist/functions/cssRuleInspector.d.ts +83 -0
  41. package/dist/functions/cssRuleInspector.js +608 -0
  42. package/dist/functions/cssRuleInspector.test.d.ts +1 -0
  43. package/dist/functions/cssRuleInspector.test.js +439 -0
  44. package/dist/functions/deduplicateLabels.test.d.ts +1 -0
  45. package/dist/functions/deduplicateLabels.test.js +178 -0
  46. package/dist/functions/enrichAncestrySourceMaps.js +0 -1
  47. package/dist/functions/extractComputedStyles.d.ts +51 -0
  48. package/dist/functions/extractComputedStyles.js +447 -0
  49. package/dist/functions/extractComputedStyles.test.d.ts +1 -0
  50. package/dist/functions/extractComputedStyles.test.js +549 -0
  51. package/dist/functions/formatAncestryChain.d.ts +8 -0
  52. package/dist/functions/formatAncestryChain.js +21 -1
  53. package/dist/functions/formatAncestryChain.test.js +18 -0
  54. package/dist/functions/getUsableName.test.d.ts +1 -0
  55. package/dist/functions/getUsableName.test.js +219 -0
  56. package/dist/functions/isCombinationModifiersPressed.test.d.ts +1 -0
  57. package/dist/functions/isCombinationModifiersPressed.test.js +192 -0
  58. package/dist/functions/mergeRects.test.js +210 -1
  59. package/dist/functions/namedSnapshots.d.ts +52 -0
  60. package/dist/functions/namedSnapshots.js +161 -0
  61. package/dist/functions/namedSnapshots.test.d.ts +1 -0
  62. package/dist/functions/namedSnapshots.test.js +85 -0
  63. package/dist/functions/normalizeFilePath.test.d.ts +1 -0
  64. package/dist/functions/normalizeFilePath.test.js +66 -0
  65. package/dist/functions/parseDataId.test.d.ts +1 -0
  66. package/dist/functions/parseDataId.test.js +101 -0
  67. package/dist/hooks/getStorage.d.ts +3 -0
  68. package/dist/hooks/getStorage.js +17 -0
  69. package/dist/hooks/useEventListeners.d.ts +15 -0
  70. package/dist/hooks/useEventListeners.js +56 -0
  71. package/dist/hooks/useLocatorStorage.d.ts +18 -0
  72. package/dist/hooks/useLocatorStorage.js +41 -0
  73. package/dist/hooks/useLocatorStorage.test.d.ts +1 -0
  74. package/dist/hooks/useLocatorStorage.test.js +124 -0
  75. package/dist/hooks/useRecordingState.d.ts +43 -0
  76. package/dist/hooks/useRecordingState.js +387 -0
  77. package/dist/hooks/useSettings.d.ts +13 -0
  78. package/dist/hooks/useSettings.js +66 -0
  79. package/dist/index.d.ts +5 -2
  80. package/dist/index.js +4 -2
  81. package/dist/initRuntime.d.ts +3 -1
  82. package/dist/initRuntime.js +4 -1
  83. package/dist/mcpBridge.d.ts +61 -0
  84. package/dist/mcpBridge.js +534 -0
  85. package/dist/mcpBridge.test.d.ts +1 -0
  86. package/dist/mcpBridge.test.js +248 -0
  87. package/dist/output.css +20 -0
  88. package/dist/visualDiff/diff.d.ts +9 -0
  89. package/dist/visualDiff/diff.js +209 -0
  90. package/dist/visualDiff/diff.test.d.ts +1 -0
  91. package/dist/visualDiff/diff.test.js +253 -0
  92. package/dist/visualDiff/settle.d.ts +3 -0
  93. package/dist/visualDiff/settle.js +50 -0
  94. package/dist/visualDiff/settle.test.d.ts +1 -0
  95. package/dist/visualDiff/settle.test.js +65 -0
  96. package/dist/visualDiff/snapshot.d.ts +4 -0
  97. package/dist/visualDiff/snapshot.js +84 -0
  98. package/dist/visualDiff/snapshot.test.d.ts +1 -0
  99. package/dist/visualDiff/snapshot.test.js +245 -0
  100. package/dist/visualDiff/types.d.ts +37 -0
  101. package/dist/visualDiff/types.js +1 -0
  102. package/package.json +2 -2
  103. package/scripts/wrapCSS.js +1 -1
  104. package/scripts/wrapImage.js +1 -1
  105. package/src/_generated_styles.ts +21 -1
  106. package/src/_generated_tree_icon.ts +1 -1
  107. package/src/adapters/HtmlElementTreeNode.ts +10 -7
  108. package/src/adapters/createTreeNode.ts +12 -51
  109. package/src/adapters/detectFramework.test.ts +73 -0
  110. package/src/adapters/detectFramework.ts +28 -0
  111. package/src/adapters/jsx/jsxAdapter.test.ts +240 -0
  112. package/src/adapters/jsx/jsxAdapter.ts +53 -106
  113. package/src/adapters/nextjs/parseNextjsDataAttributes.test.ts +212 -0
  114. package/src/adapters/nextjs/parseNextjsDataAttributes.ts +1 -1
  115. package/src/adapters/react/findDebugSource.ts +5 -6
  116. package/src/adapters/react/findFiberByHtmlElement.ts +3 -3
  117. package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +3 -0
  118. package/src/adapters/react/reactAdapter.ts +1 -2
  119. package/src/adapters/resolveAdapter.ts +4 -14
  120. package/src/adapters/svelte/svelteAdapter.test.ts +334 -0
  121. package/src/adapters/vue/vueAdapter.test.ts +259 -0
  122. package/src/browserApi.test.ts +329 -0
  123. package/src/browserApi.ts +351 -4
  124. package/src/components/RecordingPillButton.tsx +301 -0
  125. package/src/components/RecordingResults.tsx +114 -13
  126. package/src/components/Runtime.tsx +176 -621
  127. package/src/components/SettingsPanel.tsx +339 -0
  128. package/src/consoleCapture.ts +113 -0
  129. package/src/functions/cssRuleInspector.test.ts +517 -0
  130. package/src/functions/cssRuleInspector.ts +708 -0
  131. package/src/functions/deduplicateLabels.test.ts +115 -0
  132. package/src/functions/enrichAncestrySourceMaps.ts +6 -3
  133. package/src/functions/extractComputedStyles.test.ts +681 -0
  134. package/src/functions/extractComputedStyles.ts +768 -0
  135. package/src/functions/formatAncestryChain.test.ts +23 -1
  136. package/src/functions/formatAncestryChain.ts +22 -1
  137. package/src/functions/getUsableName.test.ts +242 -0
  138. package/src/functions/isCombinationModifiersPressed.test.ts +156 -0
  139. package/src/functions/mergeRects.test.ts +111 -1
  140. package/src/functions/namedSnapshots.test.ts +106 -0
  141. package/src/functions/namedSnapshots.ts +232 -0
  142. package/src/functions/normalizeFilePath.test.ts +80 -0
  143. package/src/functions/parseDataId.test.ts +125 -0
  144. package/src/hooks/getStorage.ts +26 -0
  145. package/src/hooks/useEventListeners.ts +97 -0
  146. package/src/hooks/useLocatorStorage.test.ts +127 -0
  147. package/src/hooks/useLocatorStorage.ts +60 -0
  148. package/src/hooks/useRecordingState.ts +516 -0
  149. package/src/hooks/useSettings.ts +83 -0
  150. package/src/index.ts +10 -5
  151. package/src/initRuntime.ts +5 -0
  152. package/src/mcpBridge.test.ts +260 -0
  153. package/src/mcpBridge.ts +677 -0
  154. package/src/visualDiff/diff.test.ts +167 -0
  155. package/src/visualDiff/diff.ts +242 -0
  156. package/src/visualDiff/settle.test.ts +77 -0
  157. package/src/visualDiff/settle.ts +62 -0
  158. package/src/visualDiff/snapshot.test.ts +200 -0
  159. package/src/visualDiff/snapshot.ts +119 -0
  160. package/src/visualDiff/types.ts +40 -0
  161. package/tsconfig.json +3 -1
  162. package/vitest.config.ts +18 -0
  163. package/jest.config.ts +0 -195
@@ -0,0 +1,447 @@
1
+ /**
2
+ * Extracts and formats computed styles from a DOM element.
3
+ * Output is optimized for AI consumption — minimal tokens, maximum signal.
4
+ */
5
+
6
+ // --- Types ---
7
+
8
+ // --- Property Groups (longhands read from getComputedStyle) ---
9
+
10
+ const LAYOUT_PROPERTIES = ["display", "position", "top", "right", "bottom", "left", "width", "height", "min-width", "max-width", "min-height", "max-height", "box-sizing", "overflow-x", "overflow-y", "flex-direction", "flex-wrap", "flex-grow", "flex-shrink", "flex-basis", "align-items", "align-self", "align-content", "justify-content", "justify-self", "grid-template-columns", "grid-template-rows", "grid-area", "gap", "column-gap", "row-gap", "margin-top", "margin-right", "margin-bottom", "margin-left", "padding-top", "padding-right", "padding-bottom", "padding-left", "z-index", "float", "clear"];
11
+ const VISUAL_PROPERTIES = ["background-color", "background-image", "background-size", "background-position", "color", "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", "border-top-style", "border-right-style", "border-bottom-style", "border-left-style", "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "outline-width", "outline-style", "outline-color", "box-shadow", "opacity", "visibility", "transform", "transition", "animation"];
12
+ const TYPOGRAPHY_PROPERTIES = ["font-family", "font-size", "font-weight", "font-style", "line-height", "letter-spacing", "text-align", "text-decoration", "text-transform", "white-space", "word-break"];
13
+ const INTERACTION_PROPERTIES = ["cursor", "pointer-events", "user-select"];
14
+ const SVG_PROPERTIES = ["fill", "stroke", "stroke-width", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "fill-opacity", "stroke-opacity"];
15
+ const ALL_PROPERTIES = [...LAYOUT_PROPERTIES, ...VISUAL_PROPERTIES, ...TYPOGRAPHY_PROPERTIES, ...INTERACTION_PROPERTIES];
16
+ const ALL_PROPERTIES_WITH_SVG = [...ALL_PROPERTIES, ...SVG_PROPERTIES];
17
+
18
+ // Values that are always excluded regardless of defaults comparison
19
+ const ALWAYS_EXCLUDE_VALUES = new Set(["initial", "inherit", "unset"]);
20
+
21
+ // --- Property Groups Definition ---
22
+
23
+ function getPropertyGroups(isSvg) {
24
+ const groups = [{
25
+ name: "Layout",
26
+ props: LAYOUT_PROPERTIES
27
+ }, {
28
+ name: "Visual",
29
+ props: VISUAL_PROPERTIES
30
+ }, {
31
+ name: "Typography",
32
+ props: TYPOGRAPHY_PROPERTIES
33
+ }, {
34
+ name: "Interaction",
35
+ props: INTERACTION_PROPERTIES
36
+ }];
37
+ if (isSvg) {
38
+ groups.push({
39
+ name: "SVG",
40
+ props: SVG_PROPERTIES
41
+ });
42
+ }
43
+ return groups;
44
+ }
45
+
46
+ // --- Default Detection via Temp Element ---
47
+
48
+ /**
49
+ * Cache of default computed styles per tag name (+ SVG namespace marker).
50
+ * Browser defaults for a given tag are constant within a page load, so we
51
+ * avoid creating a temporary element and triggering layout on every click.
52
+ */
53
+ const defaultStylesCache = new Map();
54
+ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
55
+ function createDefaultStyleProbe(tagName, isSvgElement) {
56
+ const createProbeElement = () => isSvgElement ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName);
57
+ const mountTarget = document.body ?? document.documentElement;
58
+ if (!mountTarget) {
59
+ return {
60
+ element: createProbeElement(),
61
+ cleanup: () => {}
62
+ };
63
+ }
64
+ const host = document.createElement("div");
65
+ if (typeof host.attachShadow === "function") {
66
+ // Reset inherited values on the host and use Shadow DOM so author CSS
67
+ // from the page does not suppress informative inherited typography.
68
+ host.style.cssText = "all:initial!important;position:fixed!important;display:block!important;visibility:hidden!important;pointer-events:none!important;left:-9999px!important;top:-9999px!important;contain:style layout paint!important;";
69
+ const root = host.attachShadow({
70
+ mode: "open"
71
+ });
72
+ const probe = createProbeElement();
73
+ root.appendChild(probe);
74
+ mountTarget.appendChild(host);
75
+ return {
76
+ element: probe,
77
+ cleanup: () => host.remove()
78
+ };
79
+ }
80
+ const probe = createProbeElement();
81
+ if (probe instanceof HTMLElement || probe instanceof SVGElement) {
82
+ probe.style.cssText = "position:fixed!important;visibility:hidden!important;pointer-events:none!important;left:-9999px!important;top:-9999px!important;width:auto!important;height:auto!important;";
83
+ }
84
+ mountTarget.appendChild(probe);
85
+ return {
86
+ element: probe,
87
+ cleanup: () => probe.remove()
88
+ };
89
+ }
90
+ function getDefaultStyles(element, properties) {
91
+ const tagName = element.tagName.toLowerCase();
92
+ const isSvgElement = element.namespaceURI === "http://www.w3.org/2000/svg";
93
+ const cacheKey = `${isSvgElement ? "svg:" : ""}${tagName}`;
94
+ const cached = defaultStylesCache.get(cacheKey);
95
+ if (cached) return cached;
96
+ const defaults = new Map();
97
+ let cleanup = () => {};
98
+ try {
99
+ const probe = createDefaultStyleProbe(tagName, isSvgElement);
100
+ cleanup = probe.cleanup;
101
+ const computed = window.getComputedStyle(probe.element);
102
+ for (const prop of properties) {
103
+ defaults.set(prop, computed.getPropertyValue(prop));
104
+ }
105
+ defaultStylesCache.set(cacheKey, defaults);
106
+ } catch {
107
+ // Fallback: empty defaults means nothing gets filtered. Do not cache
108
+ // a partial result so a future call can retry.
109
+ } finally {
110
+ cleanup();
111
+ }
112
+ return defaults;
113
+ }
114
+
115
+ // --- Shorthand Collapse Helpers ---
116
+
117
+ function collapseFourValues(top, right, bottom, left) {
118
+ if (top === right && right === bottom && bottom === left) return top;
119
+ if (top === bottom && right === left) return `${top} ${right}`;
120
+ if (right === left) return `${top} ${right} ${bottom}`;
121
+ return `${top} ${right} ${bottom} ${left}`;
122
+ }
123
+ function processGroupEntries(groupProps, values, isNonDefault) {
124
+ const entries = [];
125
+ const consumed = new Set();
126
+
127
+ // --- Margin shorthand ---
128
+ tryFourValueShorthand("margin", ["margin-top", "margin-right", "margin-bottom", "margin-left"], groupProps, values, isNonDefault, entries, consumed);
129
+
130
+ // --- Padding shorthand ---
131
+ tryFourValueShorthand("padding", ["padding-top", "padding-right", "padding-bottom", "padding-left"], groupProps, values, isNonDefault, entries, consumed);
132
+
133
+ // --- Overflow shorthand ---
134
+ tryTwoValueShorthand("overflow", ["overflow-x", "overflow-y"], groupProps, values, isNonDefault, entries, consumed);
135
+
136
+ // --- Flex shorthand ---
137
+ tryFlexShorthand(groupProps, values, isNonDefault, entries, consumed);
138
+
139
+ // --- Border shorthand ---
140
+ tryBorderShorthand(groupProps, values, isNonDefault, entries, consumed);
141
+
142
+ // --- Border-radius shorthand ---
143
+ tryFourValueShorthand("border-radius", ["border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius"], groupProps, values, isNonDefault, entries, consumed);
144
+
145
+ // --- Outline shorthand ---
146
+ tryOutlineShorthand(groupProps, values, isNonDefault, entries, consumed);
147
+
148
+ // --- Remaining individual properties ---
149
+ for (const prop of groupProps) {
150
+ if (consumed.has(prop)) continue;
151
+ const value = values[prop];
152
+ if (value && isNonDefault(prop, value)) {
153
+ entries.push({
154
+ name: prop,
155
+ value
156
+ });
157
+ }
158
+ }
159
+ return entries;
160
+ }
161
+ function tryFourValueShorthand(shorthand, longhands, groupProps, values, isNonDefault, entries, consumed) {
162
+ if (!longhands.every(l => groupProps.includes(l))) return;
163
+ const vals = longhands.map(l => values[l] || "");
164
+ const nonDefaultIndices = longhands.filter((l, i) => isNonDefault(l, vals[i]));
165
+ if (nonDefaultIndices.length === 0) {
166
+ longhands.forEach(l => consumed.add(l));
167
+ return;
168
+ }
169
+ if (nonDefaultIndices.length >= 2) {
170
+ const collapsed = collapseFourValues(vals[0], vals[1], vals[2], vals[3]);
171
+ entries.push({
172
+ name: shorthand,
173
+ value: collapsed
174
+ });
175
+ longhands.forEach(l => consumed.add(l));
176
+ }
177
+ // If only 1 non-default, let it be output as individual longhand
178
+ }
179
+ function tryTwoValueShorthand(shorthand, longhands, groupProps, values, isNonDefault, entries, consumed) {
180
+ if (!longhands.every(l => groupProps.includes(l))) return;
181
+ const a = values[longhands[0]] || "";
182
+ const b = values[longhands[1]] || "";
183
+ const aNonDefault = isNonDefault(longhands[0], a);
184
+ const bNonDefault = isNonDefault(longhands[1], b);
185
+ if (!aNonDefault && !bNonDefault) {
186
+ longhands.forEach(l => consumed.add(l));
187
+ return;
188
+ }
189
+ if (aNonDefault && bNonDefault) {
190
+ if (a === b) {
191
+ entries.push({
192
+ name: shorthand,
193
+ value: a
194
+ });
195
+ } else {
196
+ entries.push({
197
+ name: shorthand,
198
+ value: `${a} ${b}`
199
+ });
200
+ }
201
+ longhands.forEach(l => consumed.add(l));
202
+ }
203
+ // If only one is non-default, let it output individually
204
+ }
205
+ function tryFlexShorthand(groupProps, values, isNonDefault, entries, consumed) {
206
+ const longhands = ["flex-grow", "flex-shrink", "flex-basis"];
207
+ if (!longhands.every(l => groupProps.includes(l))) return;
208
+ const nonDefaultCount = longhands.filter(l => isNonDefault(l, values[l] || "")).length;
209
+ if (nonDefaultCount === 0) {
210
+ longhands.forEach(l => consumed.add(l));
211
+ return;
212
+ }
213
+ if (nonDefaultCount >= 2) {
214
+ entries.push({
215
+ name: "flex",
216
+ value: `${values["flex-grow"]} ${values["flex-shrink"]} ${values["flex-basis"]}`
217
+ });
218
+ longhands.forEach(l => consumed.add(l));
219
+ }
220
+ }
221
+ function tryBorderShorthand(groupProps, values, isNonDefault, entries, consumed) {
222
+ const sides = ["top", "right", "bottom", "left"];
223
+ const widths = sides.map(s => `border-${s}-width`);
224
+ const styles = sides.map(s => `border-${s}-style`);
225
+ const colors = sides.map(s => `border-${s}-color`);
226
+ const allProps = [...widths, ...styles, ...colors];
227
+ if (!allProps.every(p => groupProps.includes(p))) return;
228
+ const anyNonDefault = allProps.some(p => isNonDefault(p, values[p] || ""));
229
+ if (!anyNonDefault) {
230
+ allProps.forEach(p => consumed.add(p));
231
+ return;
232
+ }
233
+ const wVals = widths.map(p => values[p] || "");
234
+ const sVals = styles.map(p => values[p] || "");
235
+ const cVals = colors.map(p => values[p] || "");
236
+ const allSameW = wVals.every(v => v === wVals[0]);
237
+ const allSameS = sVals.every(v => v === sVals[0]);
238
+ const allSameC = cVals.every(v => v === cVals[0]);
239
+ if (allSameW && allSameS && allSameC) {
240
+ // All sides identical — use single shorthand
241
+ if (sVals[0] !== "none") {
242
+ entries.push({
243
+ name: "border",
244
+ value: `${wVals[0]} ${sVals[0]} ${cVals[0]}`
245
+ });
246
+ }
247
+ allProps.forEach(p => consumed.add(p));
248
+ return;
249
+ }
250
+
251
+ // Per-side shorthands for non-uniform borders
252
+ for (let i = 0; i < sides.length; i++) {
253
+ const side = sides[i];
254
+ const sideProps = [widths[i], styles[i], colors[i]];
255
+ const anySideNonDefault = sideProps.some(p => isNonDefault(p, values[p] || ""));
256
+ if (anySideNonDefault && sVals[i] !== "none") {
257
+ entries.push({
258
+ name: `border-${side}`,
259
+ value: `${wVals[i]} ${sVals[i]} ${cVals[i]}`
260
+ });
261
+ }
262
+ sideProps.forEach(p => consumed.add(p));
263
+ }
264
+ }
265
+ function tryOutlineShorthand(groupProps, values, isNonDefault, entries, consumed) {
266
+ const longhands = ["outline-width", "outline-style", "outline-color"];
267
+ if (!longhands.every(l => groupProps.includes(l))) return;
268
+ const anyNonDefault = longhands.some(l => isNonDefault(l, values[l] || ""));
269
+ if (!anyNonDefault) {
270
+ longhands.forEach(l => consumed.add(l));
271
+ return;
272
+ }
273
+ const outlineStyle = values["outline-style"] || "none";
274
+ if (outlineStyle !== "none") {
275
+ entries.push({
276
+ name: "outline",
277
+ value: `${values["outline-width"]} ${values["outline-style"]} ${values["outline-color"]}`
278
+ });
279
+ }
280
+ longhands.forEach(l => consumed.add(l));
281
+ }
282
+
283
+ // --- Diff Mode ---
284
+
285
+ // WeakRef is only guaranteed in modern runtimes (Chrome 84+, Safari 14.1+,
286
+ // Firefox 79+, Node 14.6+). Guard access so the runtime doesn't throw a
287
+ // ReferenceError in older environments; we fall back to a strong reference.
288
+ const HAS_WEAK_REF = typeof globalThis.WeakRef !== "undefined";
289
+ function makeElementHolder(element) {
290
+ if (HAS_WEAK_REF) {
291
+ const ref = new globalThis.WeakRef(element);
292
+ return {
293
+ deref: () => ref.deref()
294
+ };
295
+ }
296
+ return {
297
+ deref: () => element
298
+ };
299
+ }
300
+ let lastSnapshot = null;
301
+ function formatDiff(prev, curr, elementLabel) {
302
+ const lines = [];
303
+ const header = elementLabel ? `[ComputedStyles \u0394] ${elementLabel}` : "[ComputedStyles \u0394]";
304
+ lines.push(header);
305
+ lines.push("\u2500".repeat(Math.max(header.length, 40)));
306
+ const allProps = new Set([...Object.keys(prev.properties), ...Object.keys(curr.properties)]);
307
+ const changes = [];
308
+ for (const prop of allProps) {
309
+ const prevVal = prev.properties[prop] || "";
310
+ const currVal = curr.properties[prop] || "";
311
+ if (prevVal === currVal) continue;
312
+ if (!prevVal || ALWAYS_EXCLUDE_VALUES.has(prevVal)) {
313
+ changes.push(`+ ${prop}: ${currVal}`);
314
+ } else if (!currVal || ALWAYS_EXCLUDE_VALUES.has(currVal)) {
315
+ changes.push(`- ${prop}: ${prevVal}`);
316
+ } else {
317
+ changes.push(`~ ${prop}: ${prevVal} \u2192 ${currVal}`);
318
+ }
319
+ }
320
+ const rectKeys = ["x", "y", "width", "height"];
321
+ const rectChanges = [];
322
+ for (const key of rectKeys) {
323
+ const p = prev.boundingRect[key];
324
+ const c = curr.boundingRect[key];
325
+ if (p !== c) {
326
+ rectChanges.push(`~ ${key}: ${p} \u2192 ${c}`);
327
+ }
328
+ }
329
+ if (changes.length === 0 && rectChanges.length === 0) {
330
+ lines.push("");
331
+ lines.push("No changes detected");
332
+ } else {
333
+ if (changes.length > 0) {
334
+ lines.push("");
335
+ for (const change of changes) {
336
+ lines.push(change);
337
+ }
338
+ }
339
+ if (rectChanges.length > 0) {
340
+ lines.push("");
341
+ lines.push("BoundingRect");
342
+ for (const change of rectChanges) {
343
+ lines.push(change);
344
+ }
345
+ }
346
+ }
347
+ return lines.join("\n");
348
+ }
349
+
350
+ // --- Main Extraction Function ---
351
+
352
+ export function readSnapshot(element) {
353
+ const isSvg = element instanceof SVGElement;
354
+ const properties = isSvg ? ALL_PROPERTIES_WITH_SVG : ALL_PROPERTIES;
355
+ const computed = window.getComputedStyle(element);
356
+ const values = {};
357
+ for (const prop of properties) {
358
+ values[prop] = computed.getPropertyValue(prop);
359
+ }
360
+ const rect = element.getBoundingClientRect();
361
+ const boundingRect = {
362
+ x: Math.round(rect.x),
363
+ y: Math.round(rect.y),
364
+ width: Math.round(rect.width),
365
+ height: Math.round(rect.height),
366
+ top: Math.round(rect.top),
367
+ right: Math.round(rect.right),
368
+ bottom: Math.round(rect.bottom),
369
+ left: Math.round(rect.left)
370
+ };
371
+ return {
372
+ properties: values,
373
+ boundingRect
374
+ };
375
+ }
376
+ export function extractComputedStyles(element, elementLabel, options = {}) {
377
+ const isSvg = element instanceof SVGElement;
378
+ const properties = isSvg ? ALL_PROPERTIES_WITH_SVG : ALL_PROPERTIES;
379
+ const snapshot = readSnapshot(element);
380
+ const values = snapshot.properties;
381
+ const boundingRect = snapshot.boundingRect;
382
+ let diffMode = false;
383
+ let previousSnapshot = null;
384
+ if (lastSnapshot && !options.forceFull) {
385
+ const prevElement = lastSnapshot.elementRef.deref();
386
+ if (prevElement === element) {
387
+ diffMode = true;
388
+ previousSnapshot = lastSnapshot.snapshot;
389
+ }
390
+ }
391
+ lastSnapshot = {
392
+ elementRef: makeElementHolder(element),
393
+ snapshot
394
+ };
395
+
396
+ // Compute default filtering up front so both diff-mode and normal-mode
397
+ // return paths can hand callers a filtered snapshot.
398
+ const defaults = options.includeDefaults ? null : getDefaultStyles(element, properties);
399
+ const isNonDefault = (prop, value) => {
400
+ if (!value || value === "") return false;
401
+ if (ALWAYS_EXCLUDE_VALUES.has(value)) return false;
402
+ if (options.includeDefaults) return true;
403
+ return defaults?.get(prop) !== value;
404
+ };
405
+ const returnedSnapshot = options.includeDefaults ? snapshot : {
406
+ properties: Object.fromEntries(Object.entries(values).filter(([prop, value]) => isNonDefault(prop, value))),
407
+ boundingRect
408
+ };
409
+ if (diffMode && previousSnapshot) {
410
+ return {
411
+ formatted: formatDiff(previousSnapshot, snapshot, elementLabel),
412
+ snapshot: returnedSnapshot
413
+ };
414
+ }
415
+ const lines = [];
416
+
417
+ // Header
418
+ const header = elementLabel ? `[ComputedStyles] ${elementLabel}` : "[ComputedStyles]";
419
+ lines.push(header);
420
+ lines.push("\u2500".repeat(Math.max(header.length, 40)));
421
+
422
+ // Process each property group
423
+ const groups = getPropertyGroups(isSvg);
424
+ for (const group of groups) {
425
+ const entries = processGroupEntries(group.props, values, isNonDefault);
426
+ if (entries.length > 0) {
427
+ lines.push("");
428
+ lines.push(group.name);
429
+ for (const entry of entries) {
430
+ lines.push(` ${entry.name}: ${entry.value}`);
431
+ }
432
+ }
433
+ }
434
+
435
+ // Bounding rect always last
436
+ lines.push("");
437
+ lines.push("BoundingRect");
438
+ lines.push(` x: ${boundingRect.x} y: ${boundingRect.y} width: ${boundingRect.width} height: ${boundingRect.height}`);
439
+ return {
440
+ formatted: lines.join("\n"),
441
+ snapshot: returnedSnapshot
442
+ };
443
+ }
444
+ export { formatDiff as formatSnapshotDiff };
445
+
446
+ // Exported for testing
447
+ export { collapseFourValues as _collapseFourValues, processGroupEntries as _processGroupEntries, formatDiff as _formatDiff, LAYOUT_PROPERTIES, VISUAL_PROPERTIES, TYPOGRAPHY_PROPERTIES, INTERACTION_PROPERTIES, SVG_PROPERTIES };
@@ -0,0 +1 @@
1
+ export {};