@buoy-gg/debug-borders 2.0.2

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 (43) hide show
  1. package/README.md +334 -0
  2. package/lib/commonjs/debug-borders/components/DebugBordersModal.js +234 -0
  3. package/lib/commonjs/debug-borders/components/DebugBordersStandaloneOverlay.js +436 -0
  4. package/lib/commonjs/debug-borders/index.js +51 -0
  5. package/lib/commonjs/debug-borders/types.js +1 -0
  6. package/lib/commonjs/debug-borders/utils/DebugBordersManager.js +119 -0
  7. package/lib/commonjs/debug-borders/utils/ViewTypeMapper.js +264 -0
  8. package/lib/commonjs/debug-borders/utils/colorGeneration.js +76 -0
  9. package/lib/commonjs/debug-borders/utils/componentInfo.js +183 -0
  10. package/lib/commonjs/debug-borders/utils/componentMeasurement.js +111 -0
  11. package/lib/commonjs/debug-borders/utils/fiberTreeTraversal.js +309 -0
  12. package/lib/commonjs/debug-borders/utils/labelPositioning.js +202 -0
  13. package/lib/commonjs/index.js +34 -0
  14. package/lib/commonjs/package.json +1 -0
  15. package/lib/commonjs/preset.js +178 -0
  16. package/lib/module/debug-borders/components/DebugBordersModal.js +229 -0
  17. package/lib/module/debug-borders/components/DebugBordersStandaloneOverlay.js +432 -0
  18. package/lib/module/debug-borders/index.js +15 -0
  19. package/lib/module/debug-borders/types.js +1 -0
  20. package/lib/module/debug-borders/utils/DebugBordersManager.js +119 -0
  21. package/lib/module/debug-borders/utils/ViewTypeMapper.js +255 -0
  22. package/lib/module/debug-borders/utils/colorGeneration.js +76 -0
  23. package/lib/module/debug-borders/utils/componentInfo.js +183 -0
  24. package/lib/module/debug-borders/utils/componentMeasurement.js +111 -0
  25. package/lib/module/debug-borders/utils/fiberTreeTraversal.js +309 -0
  26. package/lib/module/debug-borders/utils/labelPositioning.js +202 -0
  27. package/lib/module/index.js +7 -0
  28. package/lib/module/preset.js +166 -0
  29. package/lib/typescript/debug-borders/components/DebugBordersModal.d.ts +11 -0
  30. package/lib/typescript/debug-borders/components/DebugBordersModal.d.ts.map +1 -0
  31. package/lib/typescript/debug-borders/components/DebugBordersStandaloneOverlay.d.ts +15 -0
  32. package/lib/typescript/debug-borders/components/DebugBordersStandaloneOverlay.d.ts.map +1 -0
  33. package/lib/typescript/debug-borders/index.d.ts +8 -0
  34. package/lib/typescript/debug-borders/index.d.ts.map +1 -0
  35. package/lib/typescript/debug-borders/types.d.ts +45 -0
  36. package/lib/typescript/debug-borders/types.d.ts.map +1 -0
  37. package/lib/typescript/debug-borders/utils/ViewTypeMapper.d.ts +66 -0
  38. package/lib/typescript/debug-borders/utils/ViewTypeMapper.d.ts.map +1 -0
  39. package/lib/typescript/index.d.ts +3 -0
  40. package/lib/typescript/index.d.ts.map +1 -0
  41. package/lib/typescript/preset.d.ts +108 -0
  42. package/lib/typescript/preset.d.ts.map +1 -0
  43. package/package.json +72 -0
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+
3
+ function getReactDevToolsHook() {
4
+ if (typeof global === "undefined") {
5
+ return null;
6
+ }
7
+ const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
8
+ if (!hook) {
9
+ return null;
10
+ }
11
+ return hook;
12
+ }
13
+ function getFiberRoots() {
14
+ const hook = getReactDevToolsHook();
15
+ if (!hook) {
16
+ return [];
17
+ }
18
+ if (!hook.getFiberRoots) {
19
+ return [];
20
+ }
21
+ try {
22
+ const rootsSet = hook.getFiberRoots(1);
23
+ if (!rootsSet) {
24
+ return [];
25
+ }
26
+ return Array.from(rootsSet);
27
+ } catch (error) {
28
+ return [];
29
+ }
30
+ }
31
+ const FiberTags = {
32
+ FunctionComponent: 0,
33
+ ClassComponent: 1,
34
+ IndeterminateComponent: 2,
35
+ HostRoot: 3,
36
+ HostPortal: 4,
37
+ HostComponent: 5,
38
+ HostText: 6,
39
+ Fragment: 7,
40
+ Mode: 8,
41
+ ContextConsumer: 9,
42
+ ContextProvider: 10,
43
+ ForwardRef: 11,
44
+ Profiler: 12,
45
+ SuspenseComponent: 13,
46
+ MemoComponent: 14,
47
+ SimpleMemoComponent: 15,
48
+ LazyComponent: 16,
49
+ IncompleteClassComponent: 17,
50
+ DehydratedFragment: 18,
51
+ SuspenseListComponent: 19,
52
+ ScopeComponent: 21,
53
+ OffscreenComponent: 22,
54
+ LegacyHiddenComponent: 23,
55
+ CacheComponent: 24,
56
+ TracingMarkerComponent: 25
57
+ };
58
+ const DEV_TOOLS_COMPONENT_NAMES = new Set(["FloatingTools", "FloatingDevTools", "FloatingMenu", "DialDevTools", "DevToolsVisibilityProvider", "AppHostProvider", "MinimizedToolsProvider", "MinimizedToolsStack", "GlitchToolButton", "DefaultConfigProvider", "JsModalComponent", "JsModal", "ModalHeader", "DraggableHeader", "DragIndicator", "CornerHandle", "WindowControls", "HighlightUpdatesModal", "HighlightUpdatesOverlay", "HighlightFilterView", "RenderDetailView", "RenderListItem", "RenderListItemInner", "RenderHistoryViewer", "RenderCauseBadge", "DebugBordersStandaloneOverlay", "DebugBordersModal", "StorageBrowser", "StorageModal", "GameUIStorageBrowser", "StorageEventListener", "StorageEventFilterView", "NetworkMonitor", "NetworkModal", "NetworkRequestList", "NetworkRequestDetail", "RouteEventsModal", "RouteEventsList", "EnvSwitcher", "EnvSwitcherModal", "TabSelector", "SectionHeader", "TypePicker", "PatternInput", "PatternChip", "DetectedItemsSection", "DetectedCategoryBadge", "IdentifierBadge", "CategoryBadge", "AppRenderer", "AppOverlay", "ExpandablePopover", "Divider", "UserStatus", "GripVerticalIcon", "LogBox", "LogBoxLog", "LogBoxLogNotification", "LogBoxNotificationContainer", "_LogBoxNotificationContainer", "LogBoxInspector", "LogBoxInspectorContainer", "LogBoxInspectorHeader", "LogBoxInspectorBody", "LogBoxInspectorFooter", "LogBoxInspectorMessageHeader", "LogBoxInspectorStackFrame", "LogBoxInspectorSection", "LogBoxButton", "LogBoxMessage"]);
59
+ const DEV_TOOLS_COMPONENT_PREFIXES = ["JsModal", "HighlightUpdates", "RenderList", "RenderDetail", "DebugBorders", "Storage", "Network", "RouteEvents", "EnvSwitcher", "Floating", "Minimized", "Expandable", "DevTools", "GameUI"];
60
+ const DEV_TOOLS_NATIVE_IDS = new Set(["debug-borders-overlay", "floating-devtools-root", "dial-devtools-root", "jsmodal-root", "highlight-updates-overlay", "__rn_buoy__highlight-modal", "logbox_inspector", "logbox"]);
61
+ const devToolsNodeCache = new Map();
62
+ const CACHE_MAX_SIZE = 500;
63
+ function getNativeTag(stateNode) {
64
+ if (!stateNode) return null;
65
+ if (typeof stateNode._nativeTag === "number") return stateNode._nativeTag;
66
+ if (typeof stateNode.canonical?._nativeTag === "number") {
67
+ return stateNode.canonical._nativeTag;
68
+ }
69
+ if (typeof stateNode.__nativeTag === "number") return stateNode.__nativeTag;
70
+ return null;
71
+ }
72
+ function isDevToolsNativeID(nativeID) {
73
+ if (!nativeID) return false;
74
+ if (DEV_TOOLS_NATIVE_IDS.has(nativeID)) return true;
75
+ const firstChar = nativeID.charCodeAt(0);
76
+ if (firstChar === 95) {
77
+ if (nativeID.startsWith("__highlight_") || nativeID.startsWith("__rn_buoy__")) {
78
+ return true;
79
+ }
80
+ }
81
+ if (firstChar === 108 && nativeID.startsWith("logbox")) {
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+ function getComponentName(fiber) {
87
+ if (!fiber) return null;
88
+ const type = fiber.type;
89
+ if (type) {
90
+ if (typeof type === "function") {
91
+ return type.displayName || type.name || null;
92
+ }
93
+ if (typeof type === "string") {
94
+ return type;
95
+ }
96
+ if (type.displayName) return type.displayName;
97
+ if (type.name) return type.name;
98
+ }
99
+ const elementType = fiber.elementType;
100
+ if (elementType) {
101
+ if (typeof elementType === "function") {
102
+ return elementType.displayName || elementType.name || null;
103
+ }
104
+ if (elementType.displayName) return elementType.displayName;
105
+ if (elementType.name) return elementType.name;
106
+ }
107
+ return null;
108
+ }
109
+ function isDevToolsComponent(fiber, stateNode) {
110
+ const nativeTag = getNativeTag(stateNode);
111
+ if (nativeTag != null) {
112
+ const cached = devToolsNodeCache.get(nativeTag);
113
+ if (cached !== undefined) {
114
+ return cached;
115
+ }
116
+ }
117
+ let result = false;
118
+ const directNativeID = fiber.pendingProps?.nativeID || fiber.memoizedProps?.nativeID;
119
+ if (isDevToolsNativeID(directNativeID)) {
120
+ result = true;
121
+ } else {
122
+ let currentFiber = fiber;
123
+ let depth = 0;
124
+ while (currentFiber && depth < 30) {
125
+ const name = getComponentName(currentFiber);
126
+ if (name) {
127
+ if (DEV_TOOLS_COMPONENT_NAMES.has(name)) {
128
+ result = true;
129
+ break;
130
+ }
131
+ for (const prefix of DEV_TOOLS_COMPONENT_PREFIXES) {
132
+ if (name.startsWith(prefix)) {
133
+ result = true;
134
+ break;
135
+ }
136
+ }
137
+ if (result) break;
138
+ }
139
+ const nativeID = currentFiber.pendingProps?.nativeID || currentFiber.memoizedProps?.nativeID;
140
+ if (isDevToolsNativeID(nativeID)) {
141
+ result = true;
142
+ break;
143
+ }
144
+ currentFiber = currentFiber.return;
145
+ depth++;
146
+ }
147
+ }
148
+ if (result && nativeTag != null) {
149
+ if (devToolsNodeCache.size >= CACHE_MAX_SIZE) {
150
+ const entries = Array.from(devToolsNodeCache.keys());
151
+ for (let i = 0; i < CACHE_MAX_SIZE / 2; i++) {
152
+ devToolsNodeCache.delete(entries[i]);
153
+ }
154
+ }
155
+ devToolsNodeCache.set(nativeTag, result);
156
+ }
157
+ return result;
158
+ }
159
+ function clearDevToolsCache() {
160
+ devToolsNodeCache.clear();
161
+ }
162
+ function isHiddenOffscreen(fiber) {
163
+ if (fiber.tag !== FiberTags.OffscreenComponent) {
164
+ return false;
165
+ }
166
+ return fiber.memoizedState !== null;
167
+ }
168
+ function isInactiveScreen(fiber) {
169
+ const props = fiber.memoizedProps;
170
+ if (!props) {
171
+ return false;
172
+ }
173
+ return props.activityState === 0;
174
+ }
175
+ function getNativeViewClassName(stateNode) {
176
+ if (!stateNode) {
177
+ return null;
178
+ }
179
+ if (stateNode.canonical?.viewConfig?.uiViewClassName) {
180
+ return stateNode.canonical.viewConfig.uiViewClassName;
181
+ }
182
+ if (stateNode.viewConfig?.uiViewClassName) {
183
+ return stateNode.viewConfig.uiViewClassName;
184
+ }
185
+ return null;
186
+ }
187
+ function isSVGComponent(stateNode) {
188
+ const viewClassName = getNativeViewClassName(stateNode);
189
+ if (!viewClassName) {
190
+ return false;
191
+ }
192
+ return viewClassName.startsWith("RNSVG");
193
+ }
194
+ function traverseFiberTree(fiber, callback, depth = 0, visited = new Set()) {
195
+ if (!fiber) {
196
+ return;
197
+ }
198
+ if (visited.has(fiber)) {
199
+ return;
200
+ }
201
+ visited.add(fiber);
202
+ if (depth > 500) {
203
+ return;
204
+ }
205
+ if (isHiddenOffscreen(fiber)) {
206
+ if (fiber.sibling) {
207
+ traverseFiberTree(fiber.sibling, callback, depth, visited);
208
+ }
209
+ return;
210
+ }
211
+ if (isInactiveScreen(fiber)) {
212
+ if (fiber.sibling) {
213
+ traverseFiberTree(fiber.sibling, callback, depth, visited);
214
+ }
215
+ return;
216
+ }
217
+ callback(fiber, depth);
218
+ if (fiber.child) {
219
+ traverseFiberTree(fiber.child, callback, depth + 1, visited);
220
+ }
221
+ if (fiber.sibling) {
222
+ traverseFiberTree(fiber.sibling, callback, depth, visited);
223
+ }
224
+ }
225
+ function getAllHostComponentInstances() {
226
+ const roots = getFiberRoots();
227
+ if (roots.length === 0) {
228
+ return [];
229
+ }
230
+ const instances = [];
231
+ roots.forEach((root, rootIndex) => {
232
+ traverseFiberTree(root.current, (fiber, depth) => {
233
+ if (fiber.tag === FiberTags.HostComponent) {
234
+ const publicInstance = fiber.stateNode;
235
+ if (isDevToolsComponent(fiber, publicInstance)) {
236
+ return;
237
+ }
238
+ if (isSVGComponent(publicInstance)) {
239
+ return;
240
+ }
241
+ if (publicInstance) {
242
+ instances.push({
243
+ instance: publicInstance,
244
+ fiber: fiber,
245
+ depth: depth
246
+ });
247
+ }
248
+ }
249
+ });
250
+ });
251
+ return instances;
252
+ }
253
+ function isReactDevToolsAvailable() {
254
+ const hook = getReactDevToolsHook();
255
+ if (!hook) {
256
+ return false;
257
+ }
258
+ if (!hook.getFiberRoots) {
259
+ return false;
260
+ }
261
+ return true;
262
+ }
263
+ function getReactDevToolsDiagnostics() {
264
+ const hook = getReactDevToolsHook();
265
+ if (!hook) {
266
+ return {
267
+ available: false,
268
+ reason: "Hook not found on global object"
269
+ };
270
+ }
271
+ const diagnostics = {
272
+ available: true,
273
+ hasAgent: !!hook.reactDevtoolsAgent,
274
+ hasFiberRoots: typeof hook.getFiberRoots === "function",
275
+ rendererCount: 0,
276
+ rootCount: 0,
277
+ hookKeys: Object.keys(hook)
278
+ };
279
+ if (hook.getFiberRoots) {
280
+ try {
281
+ const roots = hook.getFiberRoots(1);
282
+ if (roots) {
283
+ diagnostics.rootCount = roots.size;
284
+ }
285
+ } catch (e) {
286
+ diagnostics.error = e.message;
287
+ }
288
+ }
289
+ return diagnostics;
290
+ }
291
+ module.exports = {
292
+ getReactDevToolsHook,
293
+ getFiberRoots,
294
+ traverseFiberTree,
295
+ getAllHostComponentInstances,
296
+ isReactDevToolsAvailable,
297
+ getReactDevToolsDiagnostics,
298
+ isHiddenOffscreen,
299
+ isInactiveScreen,
300
+ isSVGComponent,
301
+ getNativeViewClassName,
302
+ isDevToolsComponent,
303
+ isDevToolsNativeID,
304
+ getComponentName,
305
+ clearDevToolsCache,
306
+ FiberTags,
307
+ DEV_TOOLS_COMPONENT_NAMES,
308
+ DEV_TOOLS_NATIVE_IDS
309
+ };
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Label Positioning Utility
3
+ *
4
+ * Resolves overlapping labels by repositioning them.
5
+ * Uses a greedy algorithm that processes labels once and offsets
6
+ * any that would overlap with already-placed labels.
7
+ *
8
+ * Time Complexity: O(n²) worst case, but typically much faster
9
+ * since we only compare against nearby labels and use early exit.
10
+ */
11
+
12
+ "use strict";
13
+
14
+ /**
15
+ * Configuration for label positioning
16
+ */
17
+ const CONFIG = {
18
+ // Minimum spacing between labels (pixels)
19
+ LABEL_SPACING: 0,
20
+ // Maximum vertical offset before giving up (pixels)
21
+ MAX_VERTICAL_OFFSET: 100,
22
+ // Label height estimate (used for offset increments)
23
+ LABEL_HEIGHT: 10,
24
+ // Padding around labels for collision detection
25
+ PADDING: 0
26
+ };
27
+
28
+ /**
29
+ * Check if two rectangles overlap
30
+ *
31
+ * @param {Object} a - First rectangle {x, y, width, height}
32
+ * @param {Object} b - Second rectangle {x, y, width, height}
33
+ * @returns {boolean} - True if rectangles overlap
34
+ */
35
+ function rectsOverlap(a, b) {
36
+ const padding = CONFIG.PADDING;
37
+ const aLeft = a.x - padding;
38
+ const aRight = a.x + a.width + padding;
39
+ const aTop = a.y - padding;
40
+ const aBottom = a.y + a.height + padding;
41
+ const bLeft = b.x - padding;
42
+ const bRight = b.x + b.width + padding;
43
+ const bTop = b.y - padding;
44
+ const bBottom = b.y + b.height + padding;
45
+
46
+ // No overlap if one is completely to the side or above/below the other
47
+ return !(aRight < bLeft || aLeft > bRight || aBottom < bTop || aTop > bBottom);
48
+ }
49
+
50
+ /**
51
+ * Estimate label width based on text length
52
+ * (Used when actual measurement isn't available)
53
+ *
54
+ * @param {string} text - Label text
55
+ * @returns {number} - Estimated width in pixels
56
+ */
57
+ function estimateLabelWidth(text) {
58
+ if (!text) return 0;
59
+ // Approximate: 5px per character for 8pt monospace font + padding
60
+ return text.length * 5 + 8;
61
+ }
62
+
63
+ /**
64
+ * Resolve overlapping labels by stacking them upward like a menu
65
+ *
66
+ * Strategy: Process labels in order. For each label, check if it overlaps
67
+ * with any already-placed label. If so, stack it above (going upward).
68
+ * Labels are positioned above their boxes, stacking upward with no gaps.
69
+ *
70
+ * @param {Array<Object>} rectangles - Array of rectangle data with componentInfo
71
+ * @returns {Array<Object>} - Same array with added labelOffset property
72
+ */
73
+ function resolveOverlappingLabels(rectangles) {
74
+ if (!rectangles || rectangles.length === 0) {
75
+ return rectangles;
76
+ }
77
+
78
+ // Filter to only rectangles with valid labels (testID or accessibilityLabel)
79
+ const validRectangles = rectangles.filter(rect => {
80
+ const info = rect.componentInfo;
81
+ return info && (info.testID || info.accessibilityLabel);
82
+ });
83
+
84
+ // Build label rects with estimated dimensions
85
+ // Labels are positioned ABOVE the box (y - labelHeight)
86
+ const labelRects = rectangles.map((rect, index) => {
87
+ const info = rect.componentInfo;
88
+ const hasValidLabel = info && (info.testID || info.accessibilityLabel);
89
+ const labelText = hasValidLabel ? info.primaryLabel : "";
90
+ const labelWidth = estimateLabelWidth(labelText);
91
+ return {
92
+ index,
93
+ hasValidLabel,
94
+ // Label position is above the box
95
+ x: rect.x,
96
+ y: rect.y - CONFIG.LABEL_HEIGHT,
97
+ // Position above the box
98
+ width: labelWidth,
99
+ height: CONFIG.LABEL_HEIGHT,
100
+ // Track the offset we apply (going upward, so negative)
101
+ offsetY: 0
102
+ };
103
+ });
104
+
105
+ // Sort by position (top-to-bottom, left-to-right) for consistent placement
106
+ const sortedIndices = labelRects.map((_, i) => i).filter(i => labelRects[i].hasValidLabel) // Only process valid labels
107
+ .sort((a, b) => {
108
+ const rectA = rectangles[a];
109
+ const rectB = rectangles[b];
110
+ // Primary sort by Y, secondary by X
111
+ if (Math.abs(rectA.y - rectB.y) > 10) {
112
+ return rectA.y - rectB.y;
113
+ }
114
+ return rectA.x - rectB.x;
115
+ });
116
+
117
+ // Track placed labels for collision detection
118
+ const placedLabels = [];
119
+
120
+ // Process each label in sorted order
121
+ for (const idx of sortedIndices) {
122
+ const label = labelRects[idx];
123
+
124
+ // Skip empty labels
125
+ if (label.width === 0) {
126
+ continue;
127
+ }
128
+ let offsetY = 0;
129
+ let hasOverlap = true;
130
+ let attempts = 0;
131
+ const maxAttempts = Math.ceil(CONFIG.MAX_VERTICAL_OFFSET / CONFIG.LABEL_HEIGHT);
132
+ while (hasOverlap && attempts < maxAttempts) {
133
+ // Create test rect with current offset (going upward)
134
+ const testRect = {
135
+ x: label.x,
136
+ y: label.y - offsetY,
137
+ // Subtract to go upward
138
+ width: label.width,
139
+ height: label.height
140
+ };
141
+
142
+ // Check against all placed labels
143
+ hasOverlap = placedLabels.some(placed => rectsOverlap(testRect, placed));
144
+ if (hasOverlap) {
145
+ // Stack upward with no spacing
146
+ offsetY += CONFIG.LABEL_HEIGHT;
147
+ attempts++;
148
+ }
149
+ }
150
+
151
+ // Store the offset
152
+ label.offsetY = offsetY;
153
+
154
+ // Add to placed labels with final position
155
+ placedLabels.push({
156
+ x: label.x,
157
+ y: label.y - offsetY,
158
+ width: label.width,
159
+ height: label.height
160
+ });
161
+ }
162
+
163
+ // Apply offsets to original rectangles
164
+ return rectangles.map((rect, index) => ({
165
+ ...rect,
166
+ labelOffsetY: labelRects[index].offsetY
167
+ }));
168
+ }
169
+
170
+ /**
171
+ * Quick check if any labels might overlap (for early exit optimization)
172
+ *
173
+ * @param {Array<Object>} rectangles - Array of rectangle data
174
+ * @returns {boolean} - True if there might be overlaps worth resolving
175
+ */
176
+ function mightHaveOverlaps(rectangles) {
177
+ if (!rectangles || rectangles.length < 2) {
178
+ return false;
179
+ }
180
+
181
+ // Quick heuristic: check if any boxes are close together
182
+ for (let i = 0; i < Math.min(rectangles.length, 20); i++) {
183
+ for (let j = i + 1; j < Math.min(rectangles.length, 20); j++) {
184
+ const a = rectangles[i];
185
+ const b = rectangles[j];
186
+
187
+ // If boxes are within label height of each other vertically
188
+ // and overlap horizontally, we might have label overlaps
189
+ if (Math.abs(a.y - b.y) < CONFIG.LABEL_HEIGHT * 2 && !(a.x + 100 < b.x || b.x + 100 < a.x)) {
190
+ return true;
191
+ }
192
+ }
193
+ }
194
+ return false;
195
+ }
196
+ module.exports = {
197
+ resolveOverlappingLabels,
198
+ rectsOverlap,
199
+ estimateLabelWidth,
200
+ mightHaveOverlaps,
201
+ CONFIG
202
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ // Export preset configuration (easiest way to add to FloatingDevTools!)
4
+ export { debugBordersToolPreset, createDebugBordersTool } from "./preset";
5
+
6
+ // Export all debug-borders utilities and components
7
+ export * from "./debug-borders";
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Pre-configured debug borders tool for FloatingDevTools
5
+ *
6
+ * This preset provides a zero-config way to add visual layout debugging to your dev tools.
7
+ * Just import and add it to your apps array! Tap the icon to cycle through modes:
8
+ * - Off (gray icon)
9
+ * - Borders only (green icon)
10
+ * - Borders + Labels (cyan icon)
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { debugBordersToolPreset } from '@buoy-gg/debug-borders';
15
+ *
16
+ * const installedApps = [
17
+ * debugBordersToolPreset, // That's it!
18
+ * // ...other tools
19
+ * ];
20
+ * ```
21
+ */
22
+
23
+ import React, { useState, useEffect } from "react";
24
+ import { Layers } from "@buoy-gg/shared-ui";
25
+ import { jsx as _jsx } from "react/jsx-runtime";
26
+ const DebugBordersManager = require("./debug-borders/utils/DebugBordersManager");
27
+ /**
28
+ * Mode colors for the icon
29
+ * - off: gray (disabled)
30
+ * - borders: green (enabled, borders only)
31
+ * - labels: cyan (enabled, with labels)
32
+ */
33
+ const MODE_COLORS = {
34
+ off: "#6b7280",
35
+ // Gray
36
+ borders: "#10b981",
37
+ // Green
38
+ labels: "#06b6d4" // Cyan
39
+ };
40
+
41
+ /**
42
+ * Icon component that changes color based on display mode.
43
+ *
44
+ * ⚠️ IMPORTANT - DO NOT MODIFY THIS COMPONENT ⚠️
45
+ * This component MUST use useState and useEffect hooks to subscribe to the manager.
46
+ * It is rendered as a JSX component (<IconComponent />) in FloatingMenu and DialIcon,
47
+ * which allows hooks to work properly.
48
+ *
49
+ * If you remove the hooks or change this to read getMode() directly,
50
+ * the icon color will NOT update when the toggle is pressed.
51
+ */
52
+ function BordersIcon({
53
+ size
54
+ }) {
55
+ const [mode, setMode] = useState(() => DebugBordersManager.getMode());
56
+ useEffect(() => {
57
+ const unsubscribe = DebugBordersManager.subscribe(newMode => {
58
+ setMode(newMode);
59
+ });
60
+ return unsubscribe;
61
+ }, []);
62
+ return /*#__PURE__*/_jsx(Layers, {
63
+ size: size,
64
+ color: MODE_COLORS[mode]
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Empty component for toggle-only tools (no modal needed)
70
+ */
71
+ function EmptyComponent() {
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Pre-configured debug borders tool for FloatingDevTools.
77
+ * Tap the icon to cycle through modes: Off → Borders → Labels → Off
78
+ *
79
+ * Features:
80
+ * - Visual layout debugging with colored borders
81
+ * - Optional component labels showing testID, nativeID, component name, etc.
82
+ * - Automatic component tracking
83
+ * - Real-time updates every 2 seconds
84
+ * - Icon changes color: gray (off), green (borders), cyan (labels)
85
+ */
86
+ export const debugBordersToolPreset = {
87
+ id: "debug-borders",
88
+ name: "BORDERS",
89
+ description: "Visual layout debugger - tap to cycle modes",
90
+ slot: "menu",
91
+ icon: BordersIcon,
92
+ component: EmptyComponent,
93
+ props: {},
94
+ launchMode: "toggle-only",
95
+ onPress: () => {
96
+ DebugBordersManager.cycle();
97
+ // Icon updates automatically via subscription in BordersIcon component
98
+ }
99
+ };
100
+
101
+ /**
102
+ * Create a custom debug borders tool configuration.
103
+ * Use this if you want to override default settings.
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * import { createDebugBordersTool } from '@buoy-gg/debug-borders';
108
+ *
109
+ * const myBordersTool = createDebugBordersTool({
110
+ * name: "LAYOUT",
111
+ * offColor: "#9ca3af",
112
+ * bordersColor: "#ec4899",
113
+ * labelsColor: "#8b5cf6",
114
+ * });
115
+ * ```
116
+ */
117
+ export function createDebugBordersTool(options) {
118
+ const colors = {
119
+ off: options?.offColor || "#6b7280",
120
+ borders: options?.bordersColor || "#10b981",
121
+ labels: options?.labelsColor || "#06b6d4"
122
+ };
123
+
124
+ /**
125
+ * Custom icon component with hooks - rendered as JSX component.
126
+ *
127
+ * ⚠️ IMPORTANT - DO NOT MODIFY THIS COMPONENT ⚠️
128
+ * This component MUST use useState and useEffect hooks to subscribe to the manager.
129
+ * See the comment on BordersIcon above for full explanation.
130
+ */
131
+ const CustomBordersIcon = ({
132
+ size
133
+ }) => {
134
+ const [mode, setMode] = useState(() => DebugBordersManager.getMode());
135
+ useEffect(() => {
136
+ const unsubscribe = DebugBordersManager.subscribe(newMode => {
137
+ setMode(newMode);
138
+ });
139
+ return unsubscribe;
140
+ }, []);
141
+ return /*#__PURE__*/_jsx(Layers, {
142
+ size: size,
143
+ color: colors[mode]
144
+ });
145
+ };
146
+ return {
147
+ id: options?.id || "debug-borders",
148
+ name: options?.name || "BORDERS",
149
+ description: options?.description || "Visual layout debugger - tap to cycle modes",
150
+ slot: "menu",
151
+ icon: CustomBordersIcon,
152
+ component: EmptyComponent,
153
+ props: {},
154
+ launchMode: "toggle-only",
155
+ onPress: () => {
156
+ DebugBordersManager.cycle();
157
+ // Icon updates automatically via subscription
158
+ }
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Export the standalone overlay for manual integration
164
+ * Use this if you want to control debug borders outside of FloatingDevTools
165
+ */
166
+ export { DebugBordersStandaloneOverlay } from "./debug-borders/components/DebugBordersStandaloneOverlay";
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import type { DebugBordersModalProps } from "../types";
3
+ /**
4
+ * Modal component for controlling debug borders.
5
+ * This allows developers to toggle visual layout debugging borders on/off.
6
+ */
7
+ export declare function DebugBordersModal({ visible, onClose }: DebugBordersModalProps & {
8
+ visible: boolean;
9
+ onClose: () => void;
10
+ }): React.JSX.Element | null;
11
+ //# sourceMappingURL=DebugBordersModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DebugBordersModal.d.ts","sourceRoot":"","sources":["../../../../src/debug-borders/components/DebugBordersModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAIvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,sBAAsB,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,4BAiGzH"}