@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,436 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DebugBordersStandaloneOverlay = DebugBordersStandaloneOverlay;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ /**
12
+ * Standalone Debug Borders Overlay
13
+ *
14
+ * This component renders debug borders independently of the Provider.
15
+ * It should be rendered at the root level of the app to ensure it appears on top.
16
+ *
17
+ * Supports two display modes:
18
+ * - "borders" - Shows colored borders only
19
+ * - "labels" - Shows colored borders with component labels
20
+ *
21
+ * Automatically hides borders when DevTools modals are open to avoid visual clutter.
22
+ */
23
+
24
+ // Import JsModal from shared
25
+ let JsModal = null;
26
+ let DataViewer = null;
27
+ try {
28
+ const sharedModule = require("@buoy-gg/shared-ui");
29
+ JsModal = sharedModule.JsModal;
30
+ } catch (e) {
31
+ // JsModal not available
32
+ }
33
+ try {
34
+ const dataViewerModule = require("@buoy-gg/shared-ui/dataViewer");
35
+ DataViewer = dataViewerModule.DataViewer;
36
+ } catch (e) {
37
+ // DataViewer not available
38
+ }
39
+ const DebugBordersManager = require("../utils/DebugBordersManager");
40
+ const {
41
+ getAllHostComponentInstances
42
+ } = require("../utils/fiberTreeTraversal");
43
+ const {
44
+ measureInstances
45
+ } = require("../utils/componentMeasurement");
46
+ const {
47
+ getColorForDepth
48
+ } = require("../utils/colorGeneration");
49
+ const {
50
+ getShortLabel
51
+ } = require("../utils/componentInfo");
52
+ const {
53
+ resolveOverlappingLabels
54
+ } = require("../utils/labelPositioning");
55
+
56
+ // Import DevToolsVisibility context to detect when DevTools are open
57
+ let useDevToolsVisibility = null;
58
+ try {
59
+ // Optional import - will gracefully fail if not available
60
+ const coreModule = require("@buoy-gg/core");
61
+ useDevToolsVisibility = coreModule.useDevToolsVisibility;
62
+ } catch (e) {
63
+ // DevToolsVisibility not available, that's ok - borders will always work
64
+ }
65
+ // Row component for displaying info
66
+ function InfoRow({
67
+ label,
68
+ value,
69
+ color
70
+ }) {
71
+ if (value === null || value === undefined) return null;
72
+ let displayValue;
73
+ if (typeof value === "object") {
74
+ try {
75
+ displayValue = JSON.stringify(value, null, 2);
76
+ } catch {
77
+ displayValue = String(value);
78
+ }
79
+ } else {
80
+ displayValue = String(value);
81
+ }
82
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
83
+ style: modalStyles.row,
84
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
85
+ style: modalStyles.label,
86
+ children: label
87
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
88
+ style: [modalStyles.value, color ? {
89
+ color
90
+ } : null],
91
+ children: displayValue
92
+ })]
93
+ });
94
+ }
95
+
96
+ // Simple header with just title (no hints)
97
+ function SimpleHeader({
98
+ title
99
+ }) {
100
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
101
+ style: headerStyles.container,
102
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
103
+ style: headerStyles.title,
104
+ numberOfLines: 1,
105
+ children: title
106
+ })
107
+ });
108
+ }
109
+
110
+ // Component Info Modal Content
111
+ function ComponentInfoContent({
112
+ info,
113
+ rect
114
+ }) {
115
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
116
+ style: modalStyles.content,
117
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
118
+ style: modalStyles.sectionTitle,
119
+ children: "Identifiers"
120
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
121
+ label: "testID",
122
+ value: info.testID,
123
+ color: "#10b981"
124
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
125
+ label: "accessibilityLabel",
126
+ value: info.accessibilityLabel,
127
+ color: "#ec4899"
128
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
129
+ label: "nativeID",
130
+ value: info.nativeID,
131
+ color: "#f59e0b"
132
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
133
+ label: "key",
134
+ value: info.fiberKey
135
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
136
+ style: modalStyles.sectionTitle,
137
+ children: "Component"
138
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
139
+ label: "Component Name",
140
+ value: info.componentName,
141
+ color: "#a855f7"
142
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
143
+ label: "Parent Component",
144
+ value: info.parentComponentName
145
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
146
+ label: "Display Name",
147
+ value: info.displayName
148
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
149
+ label: "Native View Type",
150
+ value: info.viewType
151
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
152
+ label: "Fiber Tag",
153
+ value: info.fiberTag
154
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
155
+ style: modalStyles.sectionTitle,
156
+ children: "Position & Size"
157
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
158
+ label: "X",
159
+ value: Math.round(rect.x)
160
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
161
+ label: "Y",
162
+ value: Math.round(rect.y)
163
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
164
+ label: "Width",
165
+ value: Math.round(rect.width)
166
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
167
+ label: "Height",
168
+ value: Math.round(rect.height)
169
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
170
+ label: "Depth",
171
+ value: rect.depth
172
+ }), (info.accessibilityRole || info.accessibilityHint || info.accessibilityState) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
173
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
174
+ style: modalStyles.sectionTitle,
175
+ children: "Accessibility"
176
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
177
+ label: "Role",
178
+ value: info.accessibilityRole
179
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
180
+ label: "Hint",
181
+ value: info.accessibilityHint
182
+ }), info.accessibilityState && DataViewer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(DataViewer, {
183
+ title: "State",
184
+ data: info.accessibilityState,
185
+ showTypeFilter: false,
186
+ initialExpanded: true
187
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(InfoRow, {
188
+ label: "State",
189
+ value: info.accessibilityState
190
+ })]
191
+ }), info.styleInfo && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
192
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
193
+ style: modalStyles.sectionTitle,
194
+ children: "Styles"
195
+ }), DataViewer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(DataViewer, {
196
+ title: "Styles",
197
+ data: info.styleInfo,
198
+ showTypeFilter: false,
199
+ initialExpanded: true
200
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
201
+ style: modalStyles.codeBlock,
202
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
203
+ style: modalStyles.codeText,
204
+ children: JSON.stringify(info.styleInfo, null, 2)
205
+ })
206
+ })]
207
+ })]
208
+ });
209
+ }
210
+ function DebugBordersStandaloneOverlay() {
211
+ const [mode, setMode] = (0, _react.useState)("off");
212
+ const [rectangles, setRectangles] = (0, _react.useState)([]);
213
+ const [selectedRect, setSelectedRect] = (0, _react.useState)(null);
214
+ const [modalVisible, setModalVisible] = (0, _react.useState)(false);
215
+ const measuringRef = _react.default.useRef(false); // Prevent overlapping measurements
216
+
217
+ // Check if any DevTools are open (if context is available)
218
+ const isDevToolsActive = useDevToolsVisibility?.()?.isDevToolsActive ?? false;
219
+ const handleLabelPress = (0, _react.useCallback)(rect => {
220
+ setSelectedRect(rect);
221
+ setModalVisible(true);
222
+ }, []);
223
+ const handleCloseModal = (0, _react.useCallback)(() => {
224
+ setModalVisible(false);
225
+ setSelectedRect(null);
226
+ }, []);
227
+
228
+ // Effective enabled state: user enabled AND no DevTools active
229
+ const isEnabled = mode !== "off" && !isDevToolsActive;
230
+ const showLabels = mode === "labels" && !isDevToolsActive;
231
+
232
+ // Subscribe to manager
233
+ (0, _react.useEffect)(() => {
234
+ const unsubscribe = DebugBordersManager.subscribe(newMode => {
235
+ setMode(newMode);
236
+ });
237
+ return unsubscribe;
238
+ }, []);
239
+
240
+ // Update measurements when enabled
241
+ (0, _react.useEffect)(() => {
242
+ if (!isEnabled) {
243
+ setRectangles([]);
244
+ return;
245
+ }
246
+ let mounted = true;
247
+ let timer;
248
+ const updateMeasurements = async () => {
249
+ if (!mounted || measuringRef.current) {
250
+ return;
251
+ }
252
+ measuringRef.current = true;
253
+ try {
254
+ const instances = getAllHostComponentInstances();
255
+ if (instances.length === 0) {
256
+ measuringRef.current = false;
257
+ return;
258
+ }
259
+ const measurements = await measureInstances(instances);
260
+ if (mounted) {
261
+ // Resolve overlapping labels when in labels mode
262
+ const processedMeasurements = showLabels ? resolveOverlappingLabels(measurements) : measurements;
263
+ setRectangles(processedMeasurements);
264
+ }
265
+ } catch (error) {
266
+ console.error("[DebugBorders] Error updating measurements:", error);
267
+ } finally {
268
+ measuringRef.current = false;
269
+ }
270
+ };
271
+
272
+ // Initial measurement with delay to let UI settle
273
+ const initialTimer = setTimeout(() => {
274
+ updateMeasurements();
275
+ }, 500);
276
+
277
+ // Periodic updates (less frequent to avoid performance issues)
278
+ timer = setInterval(updateMeasurements, 2000);
279
+ return () => {
280
+ mounted = false;
281
+ clearTimeout(initialTimer);
282
+ clearInterval(timer);
283
+ measuringRef.current = false;
284
+ };
285
+ }, [isEnabled, showLabels, rectangles.length]);
286
+ if (!isEnabled) {
287
+ return null;
288
+ }
289
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
290
+ style: styles.overlay,
291
+ pointerEvents: "box-none"
292
+ // @ts-ignore - custom prop to identify this as debug overlay
293
+ ,
294
+ dataSet: {
295
+ debugOverlay: "true"
296
+ },
297
+ nativeID: "debug-borders-overlay",
298
+ children: [rectangles.map((rect, index) => {
299
+ const info = rect.componentInfo;
300
+ const hasValidLabel = info && (info.testID || info.accessibilityLabel);
301
+
302
+ // In labels mode, only show components with testID or accessibilityLabel
303
+ if (showLabels && !hasValidLabel) {
304
+ return null;
305
+ }
306
+
307
+ // Use label color for border when in labels mode, otherwise use depth-based color
308
+ const borderColor = showLabels && info ? info.primaryColor : getColorForDepth(rect.depth);
309
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_react.default.Fragment, {
310
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
311
+ pointerEvents: "none",
312
+ style: [styles.border, {
313
+ left: rect.x,
314
+ top: rect.y,
315
+ width: rect.width,
316
+ height: rect.height,
317
+ borderColor: borderColor
318
+ }]
319
+ }), showLabels && hasValidLabel && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
320
+ activeOpacity: 0.7,
321
+ onPress: () => handleLabelPress(rect),
322
+ style: [styles.labelContainer, {
323
+ left: rect.x,
324
+ top: rect.y - 10 - (rect.labelOffsetY || 0),
325
+ backgroundColor: info.primaryColor
326
+ }],
327
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
328
+ style: styles.labelText,
329
+ numberOfLines: 1,
330
+ children: info.primaryLabel
331
+ })
332
+ })]
333
+ }, `border-${index}`);
334
+ }), JsModal && selectedRect?.componentInfo && /*#__PURE__*/(0, _jsxRuntime.jsx)(JsModal, {
335
+ visible: modalVisible,
336
+ onClose: handleCloseModal,
337
+ initialMode: "bottomSheet",
338
+ header: {
339
+ customContent: /*#__PURE__*/(0, _jsxRuntime.jsx)(SimpleHeader, {
340
+ title: selectedRect.componentInfo.primaryLabel
341
+ })
342
+ },
343
+ persistenceKey: "debug-borders-info-modal",
344
+ enablePersistence: false,
345
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ComponentInfoContent, {
346
+ info: selectedRect.componentInfo,
347
+ rect: selectedRect
348
+ })
349
+ })]
350
+ });
351
+ }
352
+ const styles = _reactNative.StyleSheet.create({
353
+ overlay: {
354
+ position: "absolute",
355
+ top: 0,
356
+ left: 0,
357
+ right: 0,
358
+ bottom: 0,
359
+ // Use z-index below floating dev tools (9999-10001) but above normal app content
360
+ zIndex: 9000,
361
+ elevation: 9000
362
+ },
363
+ border: {
364
+ position: "absolute",
365
+ borderWidth: 1,
366
+ borderStyle: "solid"
367
+ },
368
+ labelContainer: {
369
+ position: "absolute",
370
+ paddingHorizontal: 4,
371
+ paddingVertical: 1,
372
+ borderRadius: 2
373
+ },
374
+ labelText: {
375
+ fontSize: 8,
376
+ fontWeight: "700",
377
+ fontFamily: "monospace",
378
+ color: "#ffffff"
379
+ }
380
+ });
381
+ const headerStyles = _reactNative.StyleSheet.create({
382
+ container: {
383
+ paddingHorizontal: 16,
384
+ paddingVertical: 8,
385
+ alignItems: "center"
386
+ },
387
+ title: {
388
+ fontSize: 16,
389
+ fontWeight: "600",
390
+ color: "#ffffff",
391
+ fontFamily: "monospace"
392
+ }
393
+ });
394
+ const modalStyles = _reactNative.StyleSheet.create({
395
+ content: {
396
+ padding: 16
397
+ },
398
+ sectionTitle: {
399
+ fontSize: 12,
400
+ fontWeight: "700",
401
+ color: "#6b7280",
402
+ marginTop: 16,
403
+ marginBottom: 8,
404
+ textTransform: "uppercase",
405
+ letterSpacing: 1
406
+ },
407
+ row: {
408
+ flexDirection: "row",
409
+ paddingVertical: 8,
410
+ borderBottomWidth: 1,
411
+ borderBottomColor: "rgba(255, 255, 255, 0.1)"
412
+ },
413
+ label: {
414
+ fontSize: 13,
415
+ color: "#9ca3af",
416
+ width: 140,
417
+ fontFamily: "monospace"
418
+ },
419
+ value: {
420
+ fontSize: 13,
421
+ color: "#ffffff",
422
+ flex: 1,
423
+ fontFamily: "monospace"
424
+ },
425
+ codeBlock: {
426
+ backgroundColor: "rgba(0, 0, 0, 0.3)",
427
+ padding: 12,
428
+ borderRadius: 8,
429
+ marginTop: 4
430
+ },
431
+ codeText: {
432
+ fontSize: 11,
433
+ color: "#e5e7eb",
434
+ fontFamily: "monospace"
435
+ }
436
+ });
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _exportNames = {
7
+ DebugBordersManager: true,
8
+ fiberTreeTraversal: true,
9
+ componentMeasurement: true,
10
+ colorGeneration: true,
11
+ DebugBordersModal: true,
12
+ DebugBordersStandaloneOverlay: true
13
+ };
14
+ exports.DebugBordersManager = void 0;
15
+ Object.defineProperty(exports, "DebugBordersModal", {
16
+ enumerable: true,
17
+ get: function () {
18
+ return _DebugBordersModal.DebugBordersModal;
19
+ }
20
+ });
21
+ Object.defineProperty(exports, "DebugBordersStandaloneOverlay", {
22
+ enumerable: true,
23
+ get: function () {
24
+ return _DebugBordersStandaloneOverlay.DebugBordersStandaloneOverlay;
25
+ }
26
+ });
27
+ exports.fiberTreeTraversal = exports.componentMeasurement = exports.colorGeneration = void 0;
28
+ var _types = require("./types");
29
+ Object.keys(_types).forEach(function (key) {
30
+ if (key === "default" || key === "__esModule") return;
31
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
32
+ if (key in exports && exports[key] === _types[key]) return;
33
+ Object.defineProperty(exports, key, {
34
+ enumerable: true,
35
+ get: function () {
36
+ return _types[key];
37
+ }
38
+ });
39
+ });
40
+ var _DebugBordersModal = require("./components/DebugBordersModal");
41
+ var _DebugBordersStandaloneOverlay = require("./components/DebugBordersStandaloneOverlay");
42
+ // Export types
43
+
44
+ // Export components
45
+
46
+ // Export utilities (JS modules - will be typed when converted to TS)
47
+ // Note: These are CommonJS modules, so we use require for now
48
+ const DebugBordersManager = exports.DebugBordersManager = require("./utils/DebugBordersManager");
49
+ const fiberTreeTraversal = exports.fiberTreeTraversal = require("./utils/fiberTreeTraversal");
50
+ const componentMeasurement = exports.componentMeasurement = require("./utils/componentMeasurement");
51
+ const colorGeneration = exports.colorGeneration = require("./utils/colorGeneration");
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ let globalMode = "off";
4
+ const listeners = new Set();
5
+ const MODE_CYCLE_FULL = ["off", "borders", "labels"];
6
+ const MODE_CYCLE_FREE = ["off", "borders"];
7
+ let _isPro = null;
8
+ let _licenseLoadAttempted = false;
9
+ function loadLicenseModule() {
10
+ if (_licenseLoadAttempted) return;
11
+ _licenseLoadAttempted = true;
12
+ try {
13
+ const mod = require("@buoy/license");
14
+ if (mod && typeof mod.isPro === "function") {
15
+ _isPro = mod.isPro;
16
+ }
17
+ } catch {
18
+ _isPro = null;
19
+ }
20
+ }
21
+ function checkIsPro() {
22
+ loadLicenseModule();
23
+ if (_isPro) {
24
+ try {
25
+ return _isPro();
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+ function getModeCycle() {
33
+ return checkIsPro() ? MODE_CYCLE_FULL : MODE_CYCLE_FREE;
34
+ }
35
+ const MODE_CYCLE = MODE_CYCLE_FULL;
36
+ function subscribe(listener) {
37
+ listeners.add(listener);
38
+ listener(globalMode);
39
+ return () => {
40
+ listeners.delete(listener);
41
+ };
42
+ }
43
+ function notifyListeners() {
44
+ listeners.forEach(listener => {
45
+ try {
46
+ listener(globalMode);
47
+ } catch (error) {
48
+ console.error("[DebugBorders] Error in listener:", error);
49
+ }
50
+ });
51
+ }
52
+ function setMode(mode) {
53
+ if (globalMode === mode) {
54
+ return true;
55
+ }
56
+ if (mode === "labels" && !checkIsPro()) {
57
+ console.warn("[DebugBorders] Labels mode requires React Buoy Pro. Upgrade at https://buoy.gg/pro");
58
+ return false;
59
+ }
60
+ globalMode = mode;
61
+ notifyListeners();
62
+ return true;
63
+ }
64
+ function getMode() {
65
+ return globalMode;
66
+ }
67
+ function cycle() {
68
+ const availableModes = getModeCycle();
69
+ const currentIndex = availableModes.indexOf(globalMode);
70
+ if (currentIndex === -1) {
71
+ setMode("off");
72
+ return;
73
+ }
74
+ const nextIndex = (currentIndex + 1) % availableModes.length;
75
+ setMode(availableModes[nextIndex]);
76
+ }
77
+ function enable() {
78
+ setMode("borders");
79
+ }
80
+ function disable() {
81
+ setMode("off");
82
+ }
83
+ function toggle() {
84
+ cycle();
85
+ }
86
+ function isEnabled() {
87
+ return globalMode !== "off";
88
+ }
89
+ function showLabels() {
90
+ return globalMode === "labels" && checkIsPro();
91
+ }
92
+ function isProEnabled() {
93
+ return checkIsPro();
94
+ }
95
+ function getAvailableModes() {
96
+ return getModeCycle();
97
+ }
98
+ function setEnabled(enabled) {
99
+ if (enabled) {
100
+ enable();
101
+ } else {
102
+ disable();
103
+ }
104
+ }
105
+ module.exports = {
106
+ subscribe,
107
+ enable,
108
+ disable,
109
+ toggle,
110
+ cycle,
111
+ isEnabled,
112
+ showLabels,
113
+ setEnabled,
114
+ getMode,
115
+ setMode,
116
+ isProEnabled,
117
+ getAvailableModes,
118
+ MODE_CYCLE
119
+ };