@buoy-gg/shared-ui 7.0.27 → 7.0.29

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 (123) hide show
  1. package/lib/commonjs/JsModal.js +12 -2
  2. package/lib/commonjs/dataViewer/DataTreeActionDock.js +1 -0
  3. package/lib/commonjs/dataViewer/DataViewer.js +6 -3
  4. package/lib/commonjs/dataViewer/VirtualizedDataExplorer.js +13 -4
  5. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  6. package/lib/commonjs/license/FeatureGate.js +9 -0
  7. package/lib/commonjs/ui/backgrounds/registry.js +3 -2
  8. package/lib/commonjs/ui/backgrounds/variants/meteors.js +222 -78
  9. package/lib/commonjs/ui/components/Badge.js +3 -1
  10. package/lib/commonjs/ui/components/CompactRow.js +14 -1
  11. package/lib/commonjs/ui/components/CopyButton.js +9 -5
  12. package/lib/commonjs/ui/components/DevToolsCard.js +4 -1
  13. package/lib/commonjs/ui/components/DraggableHeader.js +5 -1
  14. package/lib/commonjs/ui/components/DynamicFilterView.js +23 -4
  15. package/lib/commonjs/ui/components/EventStepperFooter.js +8 -0
  16. package/lib/commonjs/ui/components/FilterComponents.js +18 -2
  17. package/lib/commonjs/ui/components/ModalHeader.js +27 -5
  18. package/lib/commonjs/ui/components/PowerToggleButton.js +2 -0
  19. package/lib/commonjs/ui/components/SearchBar.js +6 -1
  20. package/lib/commonjs/ui/components/SectionHeader.js +3 -1
  21. package/lib/commonjs/ui/components/TabSelector.js +15 -4
  22. package/lib/commonjs/ui/components/WindowControls.js +68 -96
  23. package/lib/commonjs/ui/gameUI/constants/gameUIColors.js +12 -8
  24. package/lib/module/JsModal.js +12 -2
  25. package/lib/module/dataViewer/DataTreeActionDock.js +1 -0
  26. package/lib/module/dataViewer/DataViewer.js +6 -3
  27. package/lib/module/dataViewer/VirtualizedDataExplorer.js +13 -4
  28. package/lib/module/hooks/safe-area-impl.js +1 -1
  29. package/lib/module/license/FeatureGate.js +9 -0
  30. package/lib/module/ui/backgrounds/registry.js +3 -2
  31. package/lib/module/ui/backgrounds/variants/meteors.js +224 -80
  32. package/lib/module/ui/components/Badge.js +3 -1
  33. package/lib/module/ui/components/CompactRow.js +14 -1
  34. package/lib/module/ui/components/CopyButton.js +9 -5
  35. package/lib/module/ui/components/DevToolsCard.js +4 -1
  36. package/lib/module/ui/components/DraggableHeader.js +5 -1
  37. package/lib/module/ui/components/DynamicFilterView.js +23 -4
  38. package/lib/module/ui/components/EventStepperFooter.js +8 -0
  39. package/lib/module/ui/components/FilterComponents.js +18 -2
  40. package/lib/module/ui/components/ModalHeader.js +27 -5
  41. package/lib/module/ui/components/PowerToggleButton.js +2 -0
  42. package/lib/module/ui/components/SearchBar.js +6 -1
  43. package/lib/module/ui/components/SectionHeader.js +3 -1
  44. package/lib/module/ui/components/TabSelector.js +15 -4
  45. package/lib/module/ui/components/WindowControls.js +69 -97
  46. package/lib/module/ui/gameUI/constants/gameUIColors.js +12 -8
  47. package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/dataViewer/DataTreeActionDock.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/dataViewer/DataViewer.d.ts +2 -0
  50. package/lib/typescript/commonjs/dataViewer/DataViewer.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts +9 -0
  52. package/lib/typescript/commonjs/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  54. package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/ui/backgrounds/variants/meteors.d.ts +22 -6
  57. package/lib/typescript/commonjs/ui/backgrounds/variants/meteors.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/ui/components/Badge.d.ts +3 -1
  59. package/lib/typescript/commonjs/ui/components/Badge.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts +7 -1
  61. package/lib/typescript/commonjs/ui/components/CompactRow.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/ui/components/CopyButton.d.ts +5 -0
  63. package/lib/typescript/commonjs/ui/components/CopyButton.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts +3 -1
  65. package/lib/typescript/commonjs/ui/components/DevToolsCard.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts +2 -0
  67. package/lib/typescript/commonjs/ui/components/DraggableHeader.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/ui/components/DynamicFilterView.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/ui/components/EventStepperFooter.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/ui/components/FilterComponents.d.ts +10 -2
  71. package/lib/typescript/commonjs/ui/components/FilterComponents.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts +16 -2
  73. package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts.map +1 -1
  74. package/lib/typescript/commonjs/ui/components/PowerToggleButton.d.ts +4 -1
  75. package/lib/typescript/commonjs/ui/components/PowerToggleButton.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/ui/components/SearchBar.d.ts +7 -1
  77. package/lib/typescript/commonjs/ui/components/SearchBar.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/ui/components/SectionHeader.d.ts +7 -1
  79. package/lib/typescript/commonjs/ui/components/SectionHeader.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/ui/components/TabSelector.d.ts +20 -1
  81. package/lib/typescript/commonjs/ui/components/TabSelector.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/ui/components/WindowControls.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts +5 -0
  84. package/lib/typescript/commonjs/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  85. package/lib/typescript/module/JsModal.d.ts.map +1 -1
  86. package/lib/typescript/module/dataViewer/DataTreeActionDock.d.ts.map +1 -1
  87. package/lib/typescript/module/dataViewer/DataViewer.d.ts +2 -0
  88. package/lib/typescript/module/dataViewer/DataViewer.d.ts.map +1 -1
  89. package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts +9 -0
  90. package/lib/typescript/module/dataViewer/VirtualizedDataExplorer.d.ts.map +1 -1
  91. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  92. package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
  93. package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -1
  94. package/lib/typescript/module/ui/backgrounds/variants/meteors.d.ts +22 -6
  95. package/lib/typescript/module/ui/backgrounds/variants/meteors.d.ts.map +1 -1
  96. package/lib/typescript/module/ui/components/Badge.d.ts +3 -1
  97. package/lib/typescript/module/ui/components/Badge.d.ts.map +1 -1
  98. package/lib/typescript/module/ui/components/CompactRow.d.ts +7 -1
  99. package/lib/typescript/module/ui/components/CompactRow.d.ts.map +1 -1
  100. package/lib/typescript/module/ui/components/CopyButton.d.ts +5 -0
  101. package/lib/typescript/module/ui/components/CopyButton.d.ts.map +1 -1
  102. package/lib/typescript/module/ui/components/DevToolsCard.d.ts +3 -1
  103. package/lib/typescript/module/ui/components/DevToolsCard.d.ts.map +1 -1
  104. package/lib/typescript/module/ui/components/DraggableHeader.d.ts +2 -0
  105. package/lib/typescript/module/ui/components/DraggableHeader.d.ts.map +1 -1
  106. package/lib/typescript/module/ui/components/DynamicFilterView.d.ts.map +1 -1
  107. package/lib/typescript/module/ui/components/EventStepperFooter.d.ts.map +1 -1
  108. package/lib/typescript/module/ui/components/FilterComponents.d.ts +10 -2
  109. package/lib/typescript/module/ui/components/FilterComponents.d.ts.map +1 -1
  110. package/lib/typescript/module/ui/components/ModalHeader.d.ts +16 -2
  111. package/lib/typescript/module/ui/components/ModalHeader.d.ts.map +1 -1
  112. package/lib/typescript/module/ui/components/PowerToggleButton.d.ts +4 -1
  113. package/lib/typescript/module/ui/components/PowerToggleButton.d.ts.map +1 -1
  114. package/lib/typescript/module/ui/components/SearchBar.d.ts +7 -1
  115. package/lib/typescript/module/ui/components/SearchBar.d.ts.map +1 -1
  116. package/lib/typescript/module/ui/components/SectionHeader.d.ts +7 -1
  117. package/lib/typescript/module/ui/components/SectionHeader.d.ts.map +1 -1
  118. package/lib/typescript/module/ui/components/TabSelector.d.ts +20 -1
  119. package/lib/typescript/module/ui/components/TabSelector.d.ts.map +1 -1
  120. package/lib/typescript/module/ui/components/WindowControls.d.ts.map +1 -1
  121. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts +5 -0
  122. package/lib/typescript/module/ui/gameUI/constants/gameUIColors.d.ts.map +1 -1
  123. package/package.json +4 -4
@@ -48,7 +48,8 @@ function DevToolsCard({
48
48
  activeOpacity = 0.8,
49
49
  style,
50
50
  contentStyle,
51
- testID
51
+ testID,
52
+ accessibilityLabel
52
53
  }) {
53
54
  const isInteractive = !!onPress || !!onLongPress;
54
55
 
@@ -57,6 +58,7 @@ function DevToolsCard({
57
58
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
58
59
  style: [styles.wrapper, style],
59
60
  testID: testID,
61
+ accessibilityLabel: accessibilityLabel,
60
62
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
61
63
  style: [styles.card, isSelected && styles.selectedCard, contentStyle],
62
64
  children: children
@@ -74,6 +76,7 @@ function DevToolsCard({
74
76
  activeOpacity: activeOpacity,
75
77
  disabled: disabled,
76
78
  testID: testID,
79
+ accessibilityLabel: accessibilityLabel,
77
80
  children: children
78
81
  })
79
82
  });
@@ -40,7 +40,9 @@ const DraggableHeader = exports.DraggableHeader = /*#__PURE__*/(0, _react.memo)(
40
40
  },
41
41
  style,
42
42
  enabled = true,
43
- maxOverflowX = 0
43
+ maxOverflowX = 0,
44
+ testID,
45
+ accessibilityLabel
44
46
  }) {
45
47
  const isDraggingRef = (0, _react.useRef)(false);
46
48
  const dragDistanceRef = (0, _react.useRef)(0);
@@ -190,6 +192,8 @@ const DraggableHeader = exports.DraggableHeader = /*#__PURE__*/(0, _react.memo)(
190
192
  }, [position]);
191
193
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
192
194
  style: style,
195
+ testID: testID,
196
+ accessibilityLabel: accessibilityLabel,
193
197
  ...panResponder.panHandlers,
194
198
  children: children
195
199
  });
@@ -107,6 +107,9 @@ function DynamicFilterView({
107
107
  const countIsZero = hasCount && option.count === 0;
108
108
  const showLabel = section.type !== "method" || option.icon;
109
109
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
110
+ testID: `buoy-filter-option-${option.id}`,
111
+ accessibilityRole: "button",
112
+ accessibilityLabel: String(option.label ?? option.id),
110
113
  style: [styles.filterCard, option.isActive && styles.activeFilterCard],
111
114
  onPress: () => onFilterChange?.(option.id, option.value),
112
115
  children: [option.icon ? /*#__PURE__*/(0, _jsxRuntime.jsx)(option.icon, {
@@ -191,7 +194,8 @@ function DynamicFilterView({
191
194
  children: !includeFilterManager.showAddInput ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_FilterComponents.AddFilterButton, {
192
195
  onPress: () => includeFilterManager.setShowAddInput(true),
193
196
  color: _gameUIColors.buoyColors.success,
194
- label: "Add include pattern"
197
+ label: "Add include pattern",
198
+ testID: "buoy-filter-include-add-open"
195
199
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_FilterComponents.AddFilterInput, {
196
200
  value: includeFilterManager.newFilter,
197
201
  onChange: includeFilterManager.setNewFilter,
@@ -201,9 +205,11 @@ function DynamicFilterView({
201
205
  includeFilterManager.setNewFilter("");
202
206
  },
203
207
  placeholder: includeOnlySection.placeholder || "Enter pattern to include...",
204
- color: _gameUIColors.buoyColors.success
208
+ color: _gameUIColors.buoyColors.success,
209
+ testID: "buoy-filter-include-input"
205
210
  })
206
211
  }), includeOnlySection.patterns.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
212
+ testID: "buoy-filter-include-scroll",
207
213
  style: styles.includeOnlyFiltersContent,
208
214
  showsVerticalScrollIndicator: true,
209
215
  nestedScrollEnabled: true,
@@ -248,7 +254,11 @@ function DynamicFilterView({
248
254
  color: _gameUIColors.buoyColors.primary
249
255
  })]
250
256
  })
251
- }), activePatterns.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
257
+ }), activePatterns.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView
258
+ // The active patterns live in their own short scroller, so a
259
+ // pattern added a moment ago is usually below its fold.
260
+ , {
261
+ testID: "buoy-filter-active-scroll",
252
262
  style: styles.excludeFiltersContent,
253
263
  showsVerticalScrollIndicator: true,
254
264
  nestedScrollEnabled: true,
@@ -287,6 +297,9 @@ function DynamicFilterView({
287
297
  },
288
298
  style: styles.availableKeyItem,
289
299
  "sentry-label": "ignore-touchable-opacity",
300
+ testID: `buoy-filter-available-${item}`,
301
+ accessibilityRole: "button",
302
+ accessibilityLabel: `Add filter ${item}`,
290
303
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
291
304
  style: styles.availableKeyText,
292
305
  numberOfLines: 1,
@@ -344,7 +357,13 @@ function DynamicFilterView({
344
357
  };
345
358
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
346
359
  style: styles.container,
347
- children: [renderTabs(), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
360
+ children: [renderTabs(), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView
361
+ // The filter screen's own scroller. Named because the sections it
362
+ // holds — including the active-pattern list — run past the bottom of
363
+ // a docked sheet, so reaching one means scrolling THIS, not the short
364
+ // nested scroller the pattern happens to sit in.
365
+ , {
366
+ testID: "buoy-filter-scroll",
348
367
  style: styles.content,
349
368
  contentContainerStyle: styles.scrollContent,
350
369
  showsVerticalScrollIndicator: false,
@@ -53,6 +53,9 @@ const EventStepperFooter = exports.EventStepperFooter = /*#__PURE__*/(0, _react.
53
53
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
54
54
  onPress: onPrevious,
55
55
  disabled: isFirstItem,
56
+ testID: "tool-stepper-previous",
57
+ accessibilityRole: "button",
58
+ accessibilityLabel: "Previous",
56
59
  style: [styles.navButton, isNight && styles.nightNavButton, isFirstItem && styles.navButtonDisabled],
57
60
  activeOpacity: 0.7,
58
61
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_lucideIcons.ChevronLeft, {
@@ -65,15 +68,20 @@ const EventStepperFooter = exports.EventStepperFooter = /*#__PURE__*/(0, _react.
65
68
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
66
69
  style: styles.counterContainer,
67
70
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
71
+ testID: "tool-stepper-counter",
68
72
  style: [styles.counterText, isNight && styles.nightCounterText],
69
73
  children: [itemLabel, " ", currentIndex + 1, " of ", totalItems]
70
74
  }), subtitle && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
75
+ testID: "tool-stepper-subtitle",
71
76
  style: [styles.subtitleText, isNight && styles.nightSubtitleText],
72
77
  children: subtitle
73
78
  })]
74
79
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
75
80
  onPress: onNext,
76
81
  disabled: isLastItem,
82
+ testID: "tool-stepper-next",
83
+ accessibilityRole: "button",
84
+ accessibilityLabel: "Next",
77
85
  style: [styles.navButton, isNight && styles.nightNavButton, isLastItem && styles.navButtonDisabled],
78
86
  activeOpacity: 0.7,
79
87
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
@@ -67,13 +67,16 @@ function AddFilterInput({
67
67
  onSubmit,
68
68
  onCancel,
69
69
  placeholder = "Add filter...",
70
- color = "#E5E7EB"
70
+ color = "#E5E7EB",
71
+ testID = "buoy-filter-input"
71
72
  }) {
72
73
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
73
74
  style: [styles.inputContainer, {
74
75
  borderColor: `${color}40`
75
76
  }],
76
77
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
78
+ testID: testID,
79
+ accessibilityLabel: placeholder,
77
80
  value: value,
78
81
  onChangeText: onChange,
79
82
  onSubmitEditing: onSubmit,
@@ -92,6 +95,9 @@ function AddFilterInput({
92
95
  style: styles.inputButtons,
93
96
  children: [value.trim() && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
94
97
  onPress: onSubmit,
98
+ testID: `${testID}-add`,
99
+ accessibilityLabel: "Add filter",
100
+ accessibilityRole: "button",
95
101
  style: [styles.inlineAddButton, {
96
102
  backgroundColor: `${color}15`,
97
103
  borderColor: `${color}40`
@@ -104,6 +110,9 @@ function AddFilterInput({
104
110
  })
105
111
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
106
112
  onPress: onCancel,
113
+ testID: `${testID}-cancel`,
114
+ accessibilityLabel: "Cancel adding filter",
115
+ accessibilityRole: "button",
107
116
  style: styles.cancelButton,
108
117
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
109
118
  size: 16,
@@ -119,13 +128,17 @@ function AddFilterInput({
119
128
  function AddFilterButton({
120
129
  onPress,
121
130
  color = "#E5E7EB",
122
- label = "Add Filter"
131
+ label = "Add Filter",
132
+ testID = "buoy-filter-add-open"
123
133
  }) {
124
134
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
125
135
  style: [styles.addButton, {
126
136
  borderColor: `${color}40`
127
137
  }],
128
138
  onPress: onPress,
139
+ testID: testID,
140
+ accessibilityLabel: label,
141
+ accessibilityRole: "button",
129
142
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Plus, {
130
143
  size: 14,
131
144
  color: color
@@ -154,6 +167,9 @@ function FilterList({
154
167
  style: styles.filterItemRow,
155
168
  onPress: () => onRemoveFilter?.(filter),
156
169
  activeOpacity: 0.8,
170
+ testID: `buoy-filter-active-${filter}`,
171
+ accessibilityLabel: `Remove filter ${filter}`,
172
+ accessibilityRole: "button",
157
173
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
158
174
  style: [styles.filterItemText, {
159
175
  color
@@ -43,6 +43,9 @@ function Navigation({
43
43
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
44
44
  onPress: onClose,
45
45
  style: styles.navigationButton,
46
+ testID: "tool-close",
47
+ accessibilityLabel: "Close",
48
+ accessibilityRole: "button",
46
49
  children: closeIcon || /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
47
50
  size: 20,
48
51
  color: _nightTheme.night.color.textSecondary
@@ -56,6 +59,9 @@ function Navigation({
56
59
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
57
60
  onPress: onBack,
58
61
  style: styles.navigationButton,
62
+ testID: "tool-back",
63
+ accessibilityLabel: "Back",
64
+ accessibilityRole: "button",
59
65
  children: backIcon || /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.ChevronLeft, {
60
66
  size: 20,
61
67
  color: _nightTheme.night.color.textSecondary
@@ -69,6 +75,9 @@ function Navigation({
69
75
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
70
76
  onPress: onBack,
71
77
  style: styles.navigationButton,
78
+ testID: "tool-back",
79
+ accessibilityLabel: "Back",
80
+ accessibilityRole: "button",
72
81
  children: backIcon || /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.ChevronLeft, {
73
82
  size: 20,
74
83
  color: _nightTheme.night.color.textSecondary
@@ -85,7 +94,8 @@ function Content({
85
94
  subtitle,
86
95
  children,
87
96
  centered,
88
- noMargin
97
+ noMargin,
98
+ testID
89
99
  }) {
90
100
  if (children) {
91
101
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -98,10 +108,12 @@ function Content({
98
108
  children: [title && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
99
109
  style: [styles.headerTitle, centered && styles.headerTitleCentered],
100
110
  numberOfLines: 1,
111
+ testID: testID,
101
112
  children: title
102
113
  }), subtitle && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
103
114
  style: [styles.headerSubtitle, centered && styles.headerSubtitleCentered],
104
115
  numberOfLines: 1,
116
+ testID: testID ? `${testID}-subtitle` : undefined,
105
117
  children: subtitle
106
118
  })]
107
119
  });
@@ -129,7 +141,8 @@ function HeaderSearch({
129
141
  onChangeText,
130
142
  onCancel,
131
143
  placeholder = "Search...",
132
- autoFocus = true
144
+ autoFocus = true,
145
+ testID
133
146
  }) {
134
147
  const inputRef = (0, _react.useRef)(null);
135
148
  (0, _react.useEffect)(() => {
@@ -148,10 +161,15 @@ function HeaderSearch({
148
161
  value: value,
149
162
  onChangeText: onChangeText,
150
163
  autoCorrect: false,
151
- autoCapitalize: "none"
164
+ autoCapitalize: "none",
165
+ testID: testID,
166
+ accessibilityLabel: placeholder
152
167
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
153
168
  onPress: onCancel,
154
169
  style: styles.searchCancelButton,
170
+ accessibilityRole: "button",
171
+ accessibilityLabel: "Clear search",
172
+ testID: testID ? `${testID}-clear` : undefined,
155
173
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
156
174
  size: 12,
157
175
  color: _nightTheme.night.color.textSecondary
@@ -172,7 +190,10 @@ const styles = _reactNative.StyleSheet.create({
172
190
  flex: 1,
173
191
  gap: 8,
174
192
  minHeight: 32,
175
- paddingLeft: 4
193
+ paddingLeft: 4,
194
+ // Traffic-light window controls sit absolutely in this corner. Reserve
195
+ // space so Copy / search / extra actions never paint under them.
196
+ paddingRight: 56
176
197
  },
177
198
  navigationButton: {
178
199
  padding: 4
@@ -208,7 +229,8 @@ const styles = _reactNative.StyleSheet.create({
208
229
  flexDirection: "row",
209
230
  gap: 6,
210
231
  marginLeft: "auto",
211
- marginRight: 4
232
+ marginRight: 4,
233
+ flexShrink: 0
212
234
  },
213
235
  headerContentNoMargin: {
214
236
  marginHorizontal: 0
@@ -32,6 +32,7 @@ function PowerToggleButton({
32
32
  onToggle,
33
33
  size = "medium",
34
34
  accessibilityLabel,
35
+ testID,
35
36
  disabled = false
36
37
  }) {
37
38
  const buttonSize = size === "small" ? 28 : 32;
@@ -40,6 +41,7 @@ function PowerToggleButton({
40
41
  onPress: onToggle,
41
42
  disabled: disabled,
42
43
  accessibilityLabel: accessibilityLabel,
44
+ testID: testID,
43
45
  accessibilityRole: "button",
44
46
  style: [styles.button, {
45
47
  width: buttonSize,
@@ -22,6 +22,7 @@ function SearchBar({
22
22
  containerStyle,
23
23
  autoFocus = false,
24
24
  onSubmitEditing,
25
+ testID,
25
26
  returnKeyType
26
27
  }) {
27
28
  const [isFocused, setIsFocused] = (0, _react.useState)(false);
@@ -79,10 +80,14 @@ function SearchBar({
79
80
  autoCapitalize: "none",
80
81
  autoCorrect: false,
81
82
  onSubmitEditing: onSubmitEditing,
82
- returnKeyType: returnKeyType
83
+ returnKeyType: returnKeyType,
84
+ testID: testID
83
85
  }), value.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
84
86
  onPress: handleClear,
85
87
  style: styles.clearButton,
88
+ testID: testID ? `${testID}-clear` : undefined,
89
+ accessibilityLabel: "Clear search",
90
+ accessibilityRole: "button",
86
91
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
87
92
  size: 14,
88
93
  color: _index2.gameUIColors.secondary
@@ -54,7 +54,8 @@ function Title({
54
54
  function Badge({
55
55
  count,
56
56
  color = _gameUIColors.buoyColors.primary,
57
- children
57
+ children,
58
+ testID
58
59
  }) {
59
60
  const backgroundColor = `${color}15`;
60
61
  const borderColor = `${color}33`;
@@ -64,6 +65,7 @@ function Badge({
64
65
  borderColor
65
66
  }],
66
67
  children: count !== undefined ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
68
+ testID: testID,
67
69
  style: [styles.badgeText, {
68
70
  color
69
71
  }],
@@ -19,24 +19,29 @@ var _jsxRuntime = require("react/jsx-runtime");
19
19
  function TabSelector({
20
20
  tabs,
21
21
  activeTab,
22
- onTabChange
22
+ onTabChange,
23
+ testIDPrefix
23
24
  }) {
24
25
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
25
26
  style: styles.container,
26
27
  children: tabs.map(tab => {
27
28
  const active = activeTab === tab.key;
29
+ const disabled = !!tab.disabled && !active;
28
30
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
29
31
  "sentry-label": "ignore user interaction",
32
+ testID: testIDPrefix ? `${testIDPrefix}-${tab.key}` : undefined,
30
33
  accessibilityRole: "tab",
31
34
  accessibilityState: {
32
- selected: active
35
+ selected: active,
36
+ disabled
33
37
  },
34
38
  accessibilityLabel: tab.label,
35
39
  accessibilityHint: `View ${tab.label.toLowerCase()}`,
40
+ disabled: disabled,
36
41
  onPress: () => onTabChange(tab.key),
37
- style: [styles.tabButton, active && styles.tabButtonActive],
42
+ style: [styles.tabButton, active && styles.tabButtonActive, disabled && styles.tabButtonDisabled],
38
43
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
39
- style: [styles.tabButtonText, active && styles.tabButtonTextActive],
44
+ style: [styles.tabButtonText, active && styles.tabButtonTextActive, disabled && styles.tabButtonTextDisabled],
40
45
  children: tab.label
41
46
  }), active ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
42
47
  style: styles.underline
@@ -73,6 +78,12 @@ const styles = _reactNative.StyleSheet.create({
73
78
  fontWeight: "600",
74
79
  color: _nightTheme.night.color.text
75
80
  },
81
+ tabButtonDisabled: {
82
+ opacity: 0.45
83
+ },
84
+ tabButtonTextDisabled: {
85
+ color: _nightTheme.night.color.textTertiary
86
+ },
76
87
  underline: {
77
88
  position: "absolute",
78
89
  bottom: 1.5,
@@ -112,6 +112,7 @@ function WindowControls({
112
112
  style: [styles.dot, styles.closeDot],
113
113
  activeOpacity: 0.8,
114
114
  accessibilityLabel: "Close modal",
115
+ testID: "tool-window-close",
115
116
  accessibilityRole: "button",
116
117
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
117
118
  size: ICON_SIZE,
@@ -140,10 +141,8 @@ function ExpandableWindowControls({
140
141
  }) {
141
142
  const isNight = variant === "night";
142
143
  const [expanded, setExpanded] = (0, _react.useState)(false);
143
- const [triggerLayout, setTriggerLayout] = (0, _react.useState)(null);
144
144
  const expandAnim = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
145
145
  const dismissTimeout = (0, _react.useRef)(null);
146
- const triggerRef = (0, _react.useRef)(null);
147
146
  const ToggleModeIcon = mode === "floating" ? _index.DockBottom : _index.FloatWindow;
148
147
  const toggleModeLabel = mode === "floating" ? "Dock to bottom sheet" : "Make floating window";
149
148
  const clearDismissTimer = (0, _react.useCallback)(() => {
@@ -152,38 +151,34 @@ function ExpandableWindowControls({
152
151
  dismissTimeout.current = null;
153
152
  }
154
153
  }, []);
154
+
155
+ // JS driver on purpose: native-driven animations on this node silently
156
+ // never run inside JsModal's subtree (the panel sat at opacity 0 forever,
157
+ // making the expanded controls unreachable). The popover is tiny and the
158
+ // animation is 150ms — the JS driver is fine.
155
159
  const collapse = (0, _react.useCallback)(() => {
156
160
  clearDismissTimer();
157
161
  _reactNative.Animated.spring(expandAnim, {
158
162
  toValue: 0,
159
163
  tension: 200,
160
164
  friction: 20,
161
- useNativeDriver: true
165
+ useNativeDriver: false
162
166
  }).start(() => {
163
167
  setExpanded(false);
164
168
  });
165
169
  }, [expandAnim, clearDismissTimer]);
166
170
  const expand = (0, _react.useCallback)(() => {
167
- // Measure trigger position on screen before expanding
168
- triggerRef.current?.measureInWindow((x, y, width, height) => {
169
- setTriggerLayout({
170
- x,
171
- y,
172
- width,
173
- height
174
- });
175
- setExpanded(true);
176
- _reactNative.Animated.spring(expandAnim, {
177
- toValue: 1,
178
- tension: 180,
179
- friction: 18,
180
- useNativeDriver: true
181
- }).start();
171
+ setExpanded(true);
172
+ _reactNative.Animated.spring(expandAnim, {
173
+ toValue: 1,
174
+ tension: 180,
175
+ friction: 18,
176
+ useNativeDriver: false
177
+ }).start();
182
178
 
183
- // Auto-dismiss
184
- clearDismissTimer();
185
- dismissTimeout.current = setTimeout(collapse, AUTO_DISMISS_MS);
186
- });
179
+ // Auto-dismiss
180
+ clearDismissTimer();
181
+ dismissTimeout.current = setTimeout(collapse, AUTO_DISMISS_MS);
187
182
  }, [expandAnim, collapse, clearDismissTimer]);
188
183
  (0, _react.useEffect)(() => {
189
184
  return clearDismissTimer;
@@ -202,17 +197,12 @@ function ExpandableWindowControls({
202
197
  inputRange: [0, 0.5, 1],
203
198
  outputRange: [0, 0.8, 1]
204
199
  });
205
-
206
- // Count visible buttons for layout calculation
207
- const buttonCount = 1 + (onMinimize ? 1 : 0) + (onToggleMode ? 1 : 0);
208
- const expandedWidth = buttonCount * EXPANDED_BUTTON_SIZE + (buttonCount - 1) * EXPANDED_BUTTON_SPACING + EXPANDED_PADDING * 2;
209
- const expandedHeight = EXPANDED_BUTTON_SIZE + EXPANDED_PADDING * 2;
210
200
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
211
201
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
212
- ref: triggerRef,
213
202
  onPress: expand,
214
203
  activeOpacity: 0.7,
215
204
  accessibilityLabel: "Open window controls",
205
+ testID: "tool-window-controls",
216
206
  accessibilityRole: "button",
217
207
  style: styles.trigger,
218
208
  hitSlop: {
@@ -246,71 +236,52 @@ function ExpandableWindowControls({
246
236
  })
247
237
  })]
248
238
  })
249
- }), expanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
250
- transparent: true,
251
- visible: true,
252
- statusBarTranslucent: true,
253
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback, {
254
- onPress: collapse,
255
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
256
- style: styles.overlay,
257
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
258
- style: [styles.expandedPanel, {
259
- transform: [{
260
- scale
261
- }],
262
- opacity,
263
- // Position the expanded panel anchored to trigger's top-right
264
- top: triggerLayout ? triggerLayout.y - EXPANDED_PADDING : 0,
265
- right: triggerLayout ? Math.max(4,
266
- // screen width minus trigger's right edge
267
- // We don't need Dimensions here since we use measureInWindow
268
- // which gives absolute coords. right = screenWidth - (x + width)
269
- 0) : 0,
270
- // Position using left instead, anchored to trigger's right edge
271
- left: triggerLayout ? triggerLayout.x + triggerLayout.width - expandedWidth : undefined
272
- }],
273
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback, {
274
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
275
- style: [styles.expandedContainer, isNight && styles.nightExpandedContainer],
276
- children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
277
- onPress: () => handleAction(onMinimize),
278
- style: [styles.expandedButton, isNight ? styles.nightMinimizeButton : styles.minimizeButtonExpanded],
279
- activeOpacity: 0.7,
280
- accessibilityLabel: "Minimize modal",
281
- accessibilityRole: "button",
282
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Minus, {
283
- size: EXPANDED_ICON_SIZE,
284
- color: isNight ? _nightTheme.night.color.warning : "#7A5A00",
285
- strokeWidth: 2
286
- })
287
- }), onToggleMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
288
- onPress: () => handleAction(onToggleMode),
289
- style: [styles.expandedButton, isNight ? styles.nightToggleButton : styles.expandButtonExpanded],
290
- activeOpacity: 0.7,
291
- accessibilityLabel: toggleModeLabel,
292
- accessibilityRole: "button",
293
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToggleModeIcon, {
294
- size: EXPANDED_ICON_SIZE,
295
- color: isNight ? _nightTheme.night.color.accent : "#004A1A",
296
- strokeWidth: 2
297
- })
298
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
299
- onPress: () => handleAction(onClose),
300
- style: [styles.expandedButton, isNight ? styles.nightCloseButton : styles.closeButtonExpanded],
301
- activeOpacity: 0.7,
302
- accessibilityLabel: "Close modal",
303
- accessibilityRole: "button",
304
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
305
- size: EXPANDED_ICON_SIZE,
306
- color: isNight ? _nightTheme.night.color.danger : "#4A0000",
307
- strokeWidth: 2
308
- })
309
- })]
310
- })
311
- })
239
+ }), expanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
240
+ style: [styles.expandedPanelInline, {
241
+ transform: [{
242
+ scale
243
+ }],
244
+ opacity
245
+ }],
246
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
247
+ style: [styles.expandedContainer, isNight && styles.nightExpandedContainer],
248
+ children: [onMinimize && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
249
+ onPress: () => handleAction(onMinimize),
250
+ style: [styles.expandedButton, isNight ? styles.nightMinimizeButton : styles.minimizeButtonExpanded],
251
+ activeOpacity: 0.7,
252
+ accessibilityLabel: "Minimize modal",
253
+ testID: "tool-window-minimize",
254
+ accessibilityRole: "button",
255
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Minus, {
256
+ size: EXPANDED_ICON_SIZE,
257
+ color: isNight ? _nightTheme.night.color.warning : "#7A5A00",
258
+ strokeWidth: 2
312
259
  })
313
- })
260
+ }), onToggleMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
261
+ onPress: () => handleAction(onToggleMode),
262
+ style: [styles.expandedButton, isNight ? styles.nightToggleButton : styles.expandButtonExpanded],
263
+ activeOpacity: 0.7,
264
+ accessibilityLabel: toggleModeLabel,
265
+ testID: "tool-window-mode",
266
+ accessibilityRole: "button",
267
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ToggleModeIcon, {
268
+ size: EXPANDED_ICON_SIZE,
269
+ color: isNight ? _nightTheme.night.color.accent : "#004A1A",
270
+ strokeWidth: 2
271
+ })
272
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
273
+ onPress: () => handleAction(onClose),
274
+ style: [styles.expandedButton, isNight ? styles.nightCloseButton : styles.closeButtonExpanded],
275
+ activeOpacity: 0.7,
276
+ accessibilityLabel: "Close modal",
277
+ testID: "tool-window-close",
278
+ accessibilityRole: "button",
279
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index.X, {
280
+ size: EXPANDED_ICON_SIZE,
281
+ color: isNight ? _nightTheme.night.color.danger : "#4A0000",
282
+ strokeWidth: 2
283
+ })
284
+ })]
314
285
  })
315
286
  })]
316
287
  });
@@ -345,11 +316,12 @@ const styles = _reactNative.StyleSheet.create({
345
316
  expandDot: {
346
317
  backgroundColor: COLORS.toggleMode
347
318
  },
348
- overlay: {
349
- flex: 1
350
- },
351
- expandedPanel: {
319
+ // Anchored to the windowControlsContainer (absolute top-right of the
320
+ // modal chrome); grows leftward/downward over the header.
321
+ expandedPanelInline: {
352
322
  position: "absolute",
323
+ top: -EXPANDED_PADDING,
324
+ right: -EXPANDED_PADDING,
353
325
  zIndex: 9999
354
326
  },
355
327
  expandedContainer: {