@buoy-gg/shared-ui 2.1.1 → 2.1.3

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 (92) hide show
  1. package/lib/commonjs/clipboard/clipboard-impl.js +1 -1
  2. package/lib/commonjs/dataViewer/VirtualizedDataExplorer.js +20 -23
  3. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  4. package/lib/commonjs/index.js +98 -65
  5. package/lib/commonjs/license/DeviceLimitModal.js +479 -0
  6. package/lib/commonjs/license/FeatureGate.js +4 -9
  7. package/lib/commonjs/license/LicenseEntryModal.js +205 -770
  8. package/lib/commonjs/license/index.js +0 -7
  9. package/lib/commonjs/storage/devToolsStorageKeys.js +2 -0
  10. package/lib/commonjs/stores/BaseEventStore.js +257 -0
  11. package/lib/commonjs/stores/index.js +12 -0
  12. package/lib/commonjs/ui/components/PowerToggleButton.js +73 -0
  13. package/lib/commonjs/ui/components/index.js +7 -0
  14. package/lib/commonjs/utils/index.js +27 -1
  15. package/lib/commonjs/utils/subscribable.js +113 -0
  16. package/lib/commonjs/utils/subscriberCountNotifier.js +72 -0
  17. package/lib/module/JsModal.js +1 -1
  18. package/lib/module/clipboard/clipboard-impl.js +1 -1
  19. package/lib/module/dataViewer/VirtualizedDataExplorer.js +20 -23
  20. package/lib/module/hooks/safe-area-impl.js +1 -1
  21. package/lib/module/index.js +9 -2
  22. package/lib/module/license/DeviceLimitModal.js +473 -0
  23. package/lib/module/license/FeatureGate.js +4 -9
  24. package/lib/module/license/LicenseEntryModal.js +209 -773
  25. package/lib/module/license/index.js +0 -1
  26. package/lib/module/storage/devToolsStorageKeys.js +2 -0
  27. package/lib/module/stores/BaseEventStore.js +253 -0
  28. package/lib/module/stores/index.js +7 -0
  29. package/lib/module/ui/components/PowerToggleButton.js +69 -0
  30. package/lib/module/ui/components/index.js +1 -0
  31. package/lib/module/utils/index.js +3 -1
  32. package/lib/module/utils/subscribable.js +108 -0
  33. package/lib/module/utils/subscriberCountNotifier.js +66 -0
  34. package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/clipboard/clipboard-impl.d.ts +1 -1
  36. package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  38. package/lib/typescript/commonjs/index.d.ts +4 -3
  39. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/license/DeviceLimitModal.d.ts +23 -0
  41. package/lib/typescript/commonjs/license/DeviceLimitModal.d.ts.map +1 -0
  42. package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/license/LicenseEntryModal.d.ts +10 -62
  44. package/lib/typescript/commonjs/license/LicenseEntryModal.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/license/index.d.ts +0 -2
  46. package/lib/typescript/commonjs/license/index.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +2 -0
  48. package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/stores/BaseEventStore.d.ts +145 -0
  50. package/lib/typescript/commonjs/stores/BaseEventStore.d.ts.map +1 -0
  51. package/lib/typescript/commonjs/stores/index.d.ts +5 -0
  52. package/lib/typescript/commonjs/stores/index.d.ts.map +1 -0
  53. package/lib/typescript/commonjs/ui/components/PowerToggleButton.d.ts +32 -0
  54. package/lib/typescript/commonjs/ui/components/PowerToggleButton.d.ts.map +1 -0
  55. package/lib/typescript/commonjs/ui/components/index.d.ts +2 -0
  56. package/lib/typescript/commonjs/ui/components/index.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/utils/index.d.ts +2 -0
  58. package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/utils/subscribable.d.ts +78 -0
  60. package/lib/typescript/commonjs/utils/subscribable.d.ts.map +1 -0
  61. package/lib/typescript/commonjs/utils/subscriberCountNotifier.d.ts +47 -0
  62. package/lib/typescript/commonjs/utils/subscriberCountNotifier.d.ts.map +1 -0
  63. package/lib/typescript/module/JsModal.d.ts.map +1 -1
  64. package/lib/typescript/module/clipboard/clipboard-impl.d.ts +1 -1
  65. package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  66. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  67. package/lib/typescript/module/index.d.ts +4 -3
  68. package/lib/typescript/module/index.d.ts.map +1 -1
  69. package/lib/typescript/module/license/DeviceLimitModal.d.ts +23 -0
  70. package/lib/typescript/module/license/DeviceLimitModal.d.ts.map +1 -0
  71. package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
  72. package/lib/typescript/module/license/LicenseEntryModal.d.ts +10 -62
  73. package/lib/typescript/module/license/LicenseEntryModal.d.ts.map +1 -1
  74. package/lib/typescript/module/license/index.d.ts +0 -2
  75. package/lib/typescript/module/license/index.d.ts.map +1 -1
  76. package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +2 -0
  77. package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
  78. package/lib/typescript/module/stores/BaseEventStore.d.ts +145 -0
  79. package/lib/typescript/module/stores/BaseEventStore.d.ts.map +1 -0
  80. package/lib/typescript/module/stores/index.d.ts +5 -0
  81. package/lib/typescript/module/stores/index.d.ts.map +1 -0
  82. package/lib/typescript/module/ui/components/PowerToggleButton.d.ts +32 -0
  83. package/lib/typescript/module/ui/components/PowerToggleButton.d.ts.map +1 -0
  84. package/lib/typescript/module/ui/components/index.d.ts +2 -0
  85. package/lib/typescript/module/ui/components/index.d.ts.map +1 -1
  86. package/lib/typescript/module/utils/index.d.ts +2 -0
  87. package/lib/typescript/module/utils/index.d.ts.map +1 -1
  88. package/lib/typescript/module/utils/subscribable.d.ts +78 -0
  89. package/lib/typescript/module/utils/subscribable.d.ts.map +1 -0
  90. package/lib/typescript/module/utils/subscriberCountNotifier.d.ts +47 -0
  91. package/lib/typescript/module/utils/subscriberCountNotifier.d.ts.map +1 -0
  92. package/package.json +3 -3
@@ -15,12 +15,6 @@ Object.defineProperty(exports, "LicenseEntryModal", {
15
15
  return _LicenseEntryModal.LicenseEntryModal;
16
16
  }
17
17
  });
18
- Object.defineProperty(exports, "ManageDevicesModal", {
19
- enumerable: true,
20
- get: function () {
21
- return _ManageDevicesModal.ManageDevicesModal;
22
- }
23
- });
24
18
  Object.defineProperty(exports, "ProBadge", {
25
19
  enumerable: true,
26
20
  get: function () {
@@ -61,7 +55,6 @@ Object.defineProperty(exports, "useFeatureGate", {
61
55
  });
62
56
  var _FeatureGate = require("./FeatureGate.js");
63
57
  var _LicenseEntryModal = require("./LicenseEntryModal.js");
64
- var _ManageDevicesModal = require("./ManageDevicesModal.js");
65
58
  // Centralized lazy loading for license hooks to avoid duplication across packages
66
59
  // This is the SINGLE source of truth - all packages should import from here
67
60
  let _useIsPro = null;
@@ -171,6 +171,8 @@ const devToolsStorageKeys = exports.devToolsStorageKeys = {
171
171
  modal: () => `${devToolsStorageKeys.events.root()}_modal`,
172
172
  /** Selected badge/source filters */
173
173
  enabledSources: () => `${devToolsStorageKeys.events.root()}_enabled_sources`,
174
+ /** Whether event capturing is active */
175
+ isCapturing: () => `${devToolsStorageKeys.events.root()}_is_capturing`,
174
176
  /** Copy/export settings */
175
177
  copySettings: () => `${devToolsStorageKeys.events.root()}_copy_settings`
176
178
  }
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BaseEventStore = void 0;
7
+ var _subscribable = require("../utils/subscribable.js");
8
+ var _subscriberCountNotifier = require("../utils/subscriberCountNotifier.js");
9
+ /**
10
+ * BaseEventStore - Abstract base class for event aggregation stores
11
+ *
12
+ * Handles dual subscription patterns (array listeners + individual event callbacks)
13
+ * and automatically manages start/stop lifecycle based on subscriber count.
14
+ *
15
+ * Follows TanStack Query's Subscribable pattern with extensions for:
16
+ * - Array subscriptions (get full event list on each update)
17
+ * - Individual event subscriptions (get each event as it occurs)
18
+ * - Auto-start capturing when first subscriber joins
19
+ * - Auto-stop capturing when last subscriber leaves
20
+ * - Subscriber count notifications for debugging UI
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * class StorageEventStore extends BaseEventStore<StorageEvent> {
25
+ * protected storeName = 'storage';
26
+ *
27
+ * protected startCapturing(): void {
28
+ * // Start listening to storage changes
29
+ * }
30
+ *
31
+ * protected stopCapturing(): void {
32
+ * // Stop listening to storage changes
33
+ * }
34
+ *
35
+ * isCapturing(): boolean {
36
+ * return this.unsubscribe !== null;
37
+ * }
38
+ * }
39
+ * ```
40
+ */
41
+
42
+ /**
43
+ * Callback type for receiving full events array
44
+ */
45
+
46
+ /**
47
+ * Callback type for receiving individual events
48
+ */
49
+
50
+ /**
51
+ * Configuration options for BaseEventStore
52
+ */
53
+
54
+ /**
55
+ * Abstract base class for event stores.
56
+ *
57
+ * Subclasses must implement:
58
+ * - `startCapturing()` - Begin listening to the underlying event source
59
+ * - `stopCapturing()` - Stop listening to the underlying event source
60
+ * - `isCapturing()` - Check if currently capturing events
61
+ */
62
+ class BaseEventStore extends _subscribable.Subscribable {
63
+ events = [];
64
+ arrayListeners = new Set();
65
+ constructor(options) {
66
+ super();
67
+ this.maxEvents = options.maxEvents ?? 500;
68
+ this.storeName = options.storeName;
69
+ }
70
+
71
+ // ===========================================================================
72
+ // ABSTRACT METHODS - Must be implemented by subclasses
73
+ // ===========================================================================
74
+
75
+ /**
76
+ * Start capturing events from the underlying source.
77
+ * Called automatically when first subscriber joins.
78
+ */
79
+
80
+ /**
81
+ * Stop capturing events from the underlying source.
82
+ * Called automatically when last subscriber leaves.
83
+ */
84
+
85
+ /**
86
+ * Check if the store is actively capturing events.
87
+ */
88
+
89
+ // ===========================================================================
90
+ // LIFECYCLE HOOKS - Called by Subscribable base class
91
+ // ===========================================================================
92
+
93
+ /**
94
+ * Called when first subscriber joins (via onEvent).
95
+ * Starts capturing if no one was subscribed.
96
+ */
97
+ onSubscribe() {
98
+ if (this.getTotalSubscriberCount() === 1) {
99
+ this.startCapturing();
100
+ }
101
+ (0, _subscriberCountNotifier.notifySubscriberCountChange)(this.storeName);
102
+ }
103
+
104
+ /**
105
+ * Called when a subscriber leaves (via onEvent unsubscribe).
106
+ * Stops capturing if no one is subscribed anymore.
107
+ */
108
+ onUnsubscribe() {
109
+ if (this.getTotalSubscriberCount() === 0) {
110
+ this.stopCapturing();
111
+ }
112
+ (0, _subscriberCountNotifier.notifySubscriberCountChange)(this.storeName);
113
+ }
114
+
115
+ // ===========================================================================
116
+ // SUBSCRIPTION METHODS
117
+ // ===========================================================================
118
+
119
+ /**
120
+ * Get total count of all subscribers (both individual and array listeners)
121
+ */
122
+ getTotalSubscriberCount() {
123
+ return this.listeners.size + this.arrayListeners.size;
124
+ }
125
+
126
+ /**
127
+ * Subscribe to the full events array.
128
+ * Automatically starts capturing when first subscriber joins.
129
+ *
130
+ * @param listener - Callback that receives the full events array on each update
131
+ * @returns Unsubscribe function
132
+ */
133
+ subscribeToEvents(listener) {
134
+ const wasEmpty = this.getTotalSubscriberCount() === 0;
135
+ this.arrayListeners.add(listener);
136
+
137
+ // Start capturing if this is the first subscriber
138
+ if (wasEmpty) {
139
+ this.startCapturing();
140
+ }
141
+
142
+ // Notify subscriber count change
143
+ (0, _subscriberCountNotifier.notifySubscriberCountChange)(this.storeName);
144
+
145
+ // Immediately call with current events
146
+ listener(this.getEvents());
147
+
148
+ // Return unsubscribe function
149
+ return () => {
150
+ this.arrayListeners.delete(listener);
151
+
152
+ // Stop capturing if no one is subscribed anymore
153
+ if (this.getTotalSubscriberCount() === 0) {
154
+ this.stopCapturing();
155
+ }
156
+
157
+ // Notify subscriber count change
158
+ (0, _subscriberCountNotifier.notifySubscriberCountChange)(this.storeName);
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Subscribe to individual events as they occur.
164
+ * Automatically starts capturing when first subscriber joins.
165
+ *
166
+ * @param callback - Callback that receives each event as it occurs
167
+ * @returns Unsubscribe function
168
+ */
169
+ onEvent(callback) {
170
+ // Use Subscribable's subscribe method (triggers onSubscribe hook)
171
+ return this.subscribe(callback);
172
+ }
173
+
174
+ // ===========================================================================
175
+ // EVENT MANAGEMENT
176
+ // ===========================================================================
177
+
178
+ /**
179
+ * Add an event to the store.
180
+ * Call this from subclasses when a new event is received.
181
+ *
182
+ * @param event - The event to add
183
+ */
184
+ addEvent(event) {
185
+ // Add to beginning (newest first) and limit to maxEvents
186
+ this.events = [event, ...this.events].slice(0, this.maxEvents);
187
+
188
+ // Notify individual event callbacks (via Subscribable)
189
+ this.notify(event);
190
+
191
+ // Notify array listeners
192
+ this.notifyArrayListeners();
193
+ }
194
+
195
+ /**
196
+ * Notify all array listeners of changes
197
+ */
198
+ notifyArrayListeners() {
199
+ const events = this.getEvents();
200
+ this.arrayListeners.forEach(listener => {
201
+ try {
202
+ listener(events);
203
+ } catch {
204
+ // Ignore listener errors
205
+ }
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Get all events (newest first)
211
+ */
212
+ getEvents() {
213
+ return this.events;
214
+ }
215
+
216
+ /**
217
+ * Get event count
218
+ */
219
+ getEventCount() {
220
+ return this.events.length;
221
+ }
222
+
223
+ /**
224
+ * Clear all events
225
+ */
226
+ clearEvents() {
227
+ this.events = [];
228
+ this.notifyArrayListeners();
229
+ }
230
+
231
+ /**
232
+ * Set maximum number of events to keep
233
+ */
234
+ setMaxEvents(max) {
235
+ this.maxEvents = max;
236
+ if (this.events.length > max) {
237
+ this.events = this.events.slice(0, max);
238
+ this.notifyArrayListeners();
239
+ }
240
+ }
241
+
242
+ // ===========================================================================
243
+ // DEBUGGING
244
+ // ===========================================================================
245
+
246
+ /**
247
+ * Get current subscriber counts for debugging
248
+ */
249
+ getSubscriberCounts() {
250
+ return {
251
+ eventCallbacks: this.listeners.size,
252
+ arrayListeners: this.arrayListeners.size,
253
+ total: this.getTotalSubscriberCount()
254
+ };
255
+ }
256
+ }
257
+ exports.BaseEventStore = BaseEventStore;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "BaseEventStore", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _BaseEventStore.BaseEventStore;
10
+ }
11
+ });
12
+ var _BaseEventStore = require("./BaseEventStore.js");
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PowerToggleButton = PowerToggleButton;
7
+ var _reactNative = require("react-native");
8
+ var _index = require("../../icons/index.js");
9
+ var _macOSDesignSystemColors = require("../gameUI/constants/macOSDesignSystemColors.js");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * PowerToggleButton Component
13
+ *
14
+ * A reusable power button for toggling capture/recording state across dev tools.
15
+ * Displays green when enabled, red when disabled.
16
+ */
17
+
18
+ /**
19
+ * Power toggle button used across dev tools for start/stop functionality.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <PowerToggleButton
24
+ * isEnabled={isRecording}
25
+ * onToggle={() => setIsRecording(!isRecording)}
26
+ * accessibilityLabel="Toggle network recording"
27
+ * />
28
+ * ```
29
+ */
30
+ function PowerToggleButton({
31
+ isEnabled,
32
+ onToggle,
33
+ size = "medium",
34
+ accessibilityLabel,
35
+ disabled = false
36
+ }) {
37
+ const buttonSize = size === "small" ? 28 : 32;
38
+ const iconSize = size === "small" ? 12 : 14;
39
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
40
+ onPress: onToggle,
41
+ disabled: disabled,
42
+ accessibilityLabel: accessibilityLabel,
43
+ accessibilityRole: "button",
44
+ style: [styles.button, {
45
+ width: buttonSize,
46
+ height: buttonSize
47
+ }, isEnabled ? styles.enabledButton : styles.disabledButton, disabled && styles.buttonDisabled],
48
+ activeOpacity: 0.7,
49
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Power, {
50
+ size: iconSize,
51
+ color: isEnabled ? _macOSDesignSystemColors.macOSColors.semantic.success : _macOSDesignSystemColors.macOSColors.semantic.error
52
+ })
53
+ });
54
+ }
55
+ const styles = _reactNative.StyleSheet.create({
56
+ button: {
57
+ borderRadius: 8,
58
+ borderWidth: 1,
59
+ alignItems: "center",
60
+ justifyContent: "center"
61
+ },
62
+ enabledButton: {
63
+ backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.successBackground,
64
+ borderColor: _macOSDesignSystemColors.macOSColors.semantic.success + "40"
65
+ },
66
+ disabledButton: {
67
+ backgroundColor: _macOSDesignSystemColors.macOSColors.semantic.errorBackground,
68
+ borderColor: _macOSDesignSystemColors.macOSColors.semantic.error + "40"
69
+ },
70
+ buttonDisabled: {
71
+ opacity: 0.55
72
+ }
73
+ });
@@ -225,6 +225,12 @@ Object.defineProperty(exports, "NoSearchResultsEmptyState", {
225
225
  return _EmptyState.NoSearchResultsEmptyState;
226
226
  }
227
227
  });
228
+ Object.defineProperty(exports, "PowerToggleButton", {
229
+ enumerable: true,
230
+ get: function () {
231
+ return _PowerToggleButton.PowerToggleButton;
232
+ }
233
+ });
228
234
  Object.defineProperty(exports, "SearchBar", {
229
235
  enumerable: true,
230
236
  get: function () {
@@ -333,4 +339,5 @@ var _DynamicFilterView = require("./DynamicFilterView.js");
333
339
  var _WindowControls = require("./WindowControls.js");
334
340
  var _EventStepperFooter = require("./EventStepperFooter.js");
335
341
  var _ExpandablePopover = require("./ExpandablePopover.js");
342
+ var _PowerToggleButton = require("./PowerToggleButton.js");
336
343
  var _index = require("./EventHistoryViewer/index.js");
@@ -3,6 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "Subscribable", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _subscribable.Subscribable;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "displayValue", {
7
13
  enumerable: true,
8
14
  get: function () {
@@ -105,6 +111,12 @@ Object.defineProperty(exports, "loadOptionalModule", {
105
111
  return _loadOptionalModule.loadOptionalModule;
106
112
  }
107
113
  });
114
+ Object.defineProperty(exports, "notifySubscriberCountChange", {
115
+ enumerable: true,
116
+ get: function () {
117
+ return _subscriberCountNotifier.notifySubscriberCountChange;
118
+ }
119
+ });
108
120
  Object.defineProperty(exports, "parseDisplayValue", {
109
121
  enumerable: true,
110
122
  get: function () {
@@ -129,6 +141,18 @@ Object.defineProperty(exports, "safeStringify", {
129
141
  return _safeStringify.safeStringify;
130
142
  }
131
143
  });
144
+ Object.defineProperty(exports, "subscribeToSubscriberCountChanges", {
145
+ enumerable: true,
146
+ get: function () {
147
+ return _subscriberCountNotifier.subscribeToSubscriberCountChanges;
148
+ }
149
+ });
150
+ Object.defineProperty(exports, "subscriberCountNotifier", {
151
+ enumerable: true,
152
+ get: function () {
153
+ return _subscriberCountNotifier.subscriberCountNotifier;
154
+ }
155
+ });
132
156
  Object.defineProperty(exports, "truncateText", {
133
157
  enumerable: true,
134
158
  get: function () {
@@ -141,4 +165,6 @@ var _persistentStorage = require("./persistentStorage.js");
141
165
  var _safeStringify = require("./safeStringify.js");
142
166
  var _typeHelpers = require("./typeHelpers.js");
143
167
  var _valueFormatting = require("./valueFormatting.js");
144
- var _loadOptionalModule = require("./loadOptionalModule.js");
168
+ var _loadOptionalModule = require("./loadOptionalModule.js");
169
+ var _subscribable = require("./subscribable.js");
170
+ var _subscriberCountNotifier = require("./subscriberCountNotifier.js");
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Subscribable = void 0;
7
+ /**
8
+ * Subscribable - Base class for pub/sub pattern with lifecycle hooks
9
+ *
10
+ * Ported from TanStack Query's subscription architecture.
11
+ * Provides a foundation for self-managing listeners that automatically
12
+ * start when the first subscriber joins and stop when the last leaves.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * class NetworkEventStore extends Subscribable<NetworkEventListener> {
17
+ * protected onSubscribe(): void {
18
+ * if (this.listeners.size === 1) {
19
+ * // First subscriber - start listening
20
+ * this.startNetworkListener();
21
+ * }
22
+ * }
23
+ *
24
+ * protected onUnsubscribe(): void {
25
+ * if (this.listeners.size === 0) {
26
+ * // Last subscriber left - stop listening
27
+ * this.stopNetworkListener();
28
+ * }
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+
36
+ class Subscribable {
37
+ listeners = new Set();
38
+ constructor() {
39
+ this.subscribe = this.subscribe.bind(this);
40
+ }
41
+
42
+ /**
43
+ * Subscribe to updates.
44
+ * @param listener - Callback function to receive updates
45
+ * @returns Unsubscribe function
46
+ */
47
+ subscribe(listener) {
48
+ this.listeners.add(listener);
49
+ this.onSubscribe();
50
+ return () => {
51
+ this.listeners.delete(listener);
52
+ this.onUnsubscribe();
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Check if there are any active listeners.
58
+ */
59
+ hasListeners() {
60
+ return this.listeners.size > 0;
61
+ }
62
+
63
+ /**
64
+ * Get the current number of listeners.
65
+ */
66
+ getListenerCount() {
67
+ return this.listeners.size;
68
+ }
69
+
70
+ /**
71
+ * Notify all listeners with given arguments.
72
+ */
73
+ notify(...args) {
74
+ this.listeners.forEach(listener => {
75
+ listener(...args);
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Called when a listener is added.
81
+ * Override to start resources when first subscriber joins.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * protected onSubscribe(): void {
86
+ * if (this.listeners.size === 1) {
87
+ * this.startCapturing();
88
+ * }
89
+ * }
90
+ * ```
91
+ */
92
+ onSubscribe() {
93
+ // Override in subclasses
94
+ }
95
+
96
+ /**
97
+ * Called when a listener is removed.
98
+ * Override to stop resources when last subscriber leaves.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * protected onUnsubscribe(): void {
103
+ * if (this.listeners.size === 0) {
104
+ * this.stopCapturing();
105
+ * }
106
+ * }
107
+ * ```
108
+ */
109
+ onUnsubscribe() {
110
+ // Override in subclasses
111
+ }
112
+ }
113
+ exports.Subscribable = Subscribable;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.subscriberCountNotifier = exports.subscribeToSubscriberCountChanges = exports.notifySubscriberCountChange = void 0;
7
+ /**
8
+ * Subscriber Count Notifier
9
+ *
10
+ * Global notification system for subscriber count changes across all event stores.
11
+ * Follows TanStack Query's pattern where stores notify when observers are added/removed,
12
+ * allowing UI to update instantly instead of polling.
13
+ *
14
+ * This is in shared-ui to avoid circular dependencies between packages.
15
+ *
16
+ * Usage in event stores:
17
+ * ```typescript
18
+ * import { notifySubscriberCountChange } from '@buoy-gg/shared-ui';
19
+ *
20
+ * protected onSubscribe(): void {
21
+ * // ... existing logic
22
+ * notifySubscriberCountChange('storage');
23
+ * }
24
+ * ```
25
+ *
26
+ * Usage in UI:
27
+ * ```typescript
28
+ * useEffect(() => {
29
+ * return subscribeToSubscriberCountChanges(() => {
30
+ * // Refresh subscriber counts
31
+ * });
32
+ * }, []);
33
+ * ```
34
+ */
35
+
36
+ class SubscriberCountNotifier {
37
+ listeners = new Set();
38
+
39
+ /**
40
+ * Subscribe to subscriber count changes.
41
+ * Called whenever any event store's subscriber count changes.
42
+ */
43
+ subscribe(listener) {
44
+ this.listeners.add(listener);
45
+ return () => {
46
+ this.listeners.delete(listener);
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Notify all listeners that a store's subscriber count changed.
52
+ * Called by event stores in their onSubscribe/onUnsubscribe hooks.
53
+ */
54
+ notify(sourceId) {
55
+ this.listeners.forEach(listener => {
56
+ try {
57
+ listener(sourceId);
58
+ } catch {
59
+ // Ignore listener errors
60
+ }
61
+ });
62
+ }
63
+ }
64
+
65
+ // Singleton instance
66
+ const subscriberCountNotifier = exports.subscriberCountNotifier = new SubscriberCountNotifier();
67
+
68
+ // Convenience exports
69
+ const subscribeToSubscriberCountChanges = listener => subscriberCountNotifier.subscribe(listener);
70
+ exports.subscribeToSubscriberCountChanges = subscribeToSubscriberCountChanges;
71
+ const notifySubscriberCountChange = sourceId => subscriberCountNotifier.notify(sourceId);
72
+ exports.notifySubscriberCountChange = notifySubscriberCountChange;
@@ -20,10 +20,10 @@ import { DraggableHeader, ModalHintBanner, WindowControls } from "./ui/component
20
20
  import { persistentStorage } from "./utils/persistentStorage.js";
21
21
  import { devToolsStorageKeys } from "./storage/devToolsStorageKeys.js";
22
22
  import { useHintsDisabled } from "./context/index.js";
23
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
23
24
  // ============================================================================
24
25
  // CONSTANTS - Modal dimensions and configuration
25
26
  // ============================================================================
26
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
27
  const SCREEN = Dimensions.get("window");
28
28
  const MIN_HEIGHT = 100;
29
29
  const DEFAULT_HEIGHT = 400;
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Auto-generated clipboard implementation
5
5
  * Detected: none
6
- * Generated at: 2026-01-12T04:02:16.197Z
6
+ * Generated at: 2026-02-03T00:33:15.804Z
7
7
  *
8
8
  * DO NOT EDIT - This file is generated by scripts/detect-clipboard.js
9
9
  *