@buoy-gg/core 7.0.26 → 7.0.28

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 (48) hide show
  1. package/bin/buoy.js +6 -0
  2. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +532 -1520
  3. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +541 -566
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +5 -1
  5. package/lib/commonjs/floatingMenu/ToolHost.js +2 -1
  6. package/lib/commonjs/floatingMenu/autoExternalSync.js +11 -8
  7. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +50 -21
  8. package/lib/commonjs/floatingMenu/dial/DialHyperspace.js +177 -0
  9. package/lib/commonjs/floatingMenu/dial/DialIcon.js +31 -0
  10. package/lib/commonjs/floatingMenu/dial/DialSnow.js +92 -0
  11. package/lib/commonjs/version.js +1 -1
  12. package/lib/module/floatingMenu/DevToolsSettingsModal.js +533 -1524
  13. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +542 -569
  14. package/lib/module/floatingMenu/FloatingDevTools.web.js +5 -1
  15. package/lib/module/floatingMenu/ToolHost.js +2 -1
  16. package/lib/module/floatingMenu/autoExternalSync.js +11 -8
  17. package/lib/module/floatingMenu/dial/DialDevTools.js +52 -23
  18. package/lib/module/floatingMenu/dial/DialHyperspace.js +173 -0
  19. package/lib/module/floatingMenu/dial/DialIcon.js +33 -1
  20. package/lib/module/floatingMenu/dial/DialSnow.js +90 -0
  21. package/lib/module/version.js +1 -1
  22. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +19 -13
  23. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -3
  25. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/floatingMenu/autoExternalSync.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts +24 -0
  30. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts.map +1 -0
  31. package/lib/typescript/commonjs/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts +23 -0
  33. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts.map +1 -0
  34. package/lib/typescript/commonjs/version.d.ts +1 -1
  35. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +19 -13
  36. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  37. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -3
  38. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  39. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  40. package/lib/typescript/module/floatingMenu/autoExternalSync.d.ts.map +1 -1
  41. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  42. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts +24 -0
  43. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts.map +1 -0
  44. package/lib/typescript/module/floatingMenu/dial/DialIcon.d.ts.map +1 -1
  45. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts +23 -0
  46. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts.map +1 -0
  47. package/lib/typescript/module/version.d.ts +1 -1
  48. package/package.json +5 -5
@@ -240,7 +240,11 @@ function FloatingDevToolsInner({
240
240
  id: tool.id,
241
241
  name: tool.name || (0, _floatingToolsReact.getToolLabel)(tool.id),
242
242
  description: tool.description || (0, _floatingToolsReact.getToolDescription)(tool.id),
243
- slot: tool.slot
243
+ slot: tool.slot,
244
+ // Settings rows fall back to the tool's own icon when the modal has
245
+ // no hardcoded glyph for it (mirrors native DialDevTools).
246
+ icon: tool.icon,
247
+ color: tool.color
244
248
  })), [tools]);
245
249
 
246
250
  // Welcome tooltip — native FloatingMenu.tsx:134-157: 1000ms after mount,
@@ -241,7 +241,8 @@ const styles = _reactNative.StyleSheet.create({
241
241
  bodyRoot: {
242
242
  flex: 1,
243
243
  minHeight: 0,
244
- backgroundColor: _sharedUi.buoyColors.base
244
+ // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
245
+ backgroundColor: "transparent"
245
246
  },
246
247
  toast: {
247
248
  position: "absolute",
@@ -45,7 +45,6 @@ let zustandModule = null;
45
45
  let jotaiModule = null;
46
46
  let eventsModule = null;
47
47
  let consoleModule = null;
48
- let timeMachineModule = null;
49
48
  let envModule = null;
50
49
  let highlightUpdatesModule = null;
51
50
  let debugBordersModule = null;
@@ -109,11 +108,6 @@ try {
109
108
  } catch (e) {
110
109
  (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/console", e);
111
110
  }
112
- try {
113
- timeMachineModule = require("@buoy-gg/time-machine");
114
- } catch (e) {
115
- (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/time-machine", e);
116
- }
117
111
  try {
118
112
  envModule = require("@buoy-gg/env");
119
113
  } catch (e) {
@@ -273,8 +267,17 @@ function AutoExternalSync({
273
267
  if (consoleModule?.consoleSyncAdapter) {
274
268
  map.console = consoleModule.consoleSyncAdapter;
275
269
  }
276
- if (timeMachineModule?.timeMachineSyncAdapter) {
277
- map["time-machine"] = timeMachineModule.timeMachineSyncAdapter;
270
+ // Lazy: time-machine imports `@buoy-gg/core` (useToolHost), and this file
271
+ // is loaded from core's index. Requiring it at module top-level is a
272
+ // require cycle (LogBox on every reload). Waiting until first render
273
+ // lets core finish evaluating first.
274
+ try {
275
+ const timeMachineModule = require("@buoy-gg/time-machine");
276
+ if (timeMachineModule?.timeMachineSyncAdapter) {
277
+ map["time-machine"] = timeMachineModule.timeMachineSyncAdapter;
278
+ }
279
+ } catch (e) {
280
+ (0, _optionalRequire.reportOptionalRequireError)("@buoy-gg/time-machine", e);
278
281
  }
279
282
  if (envModule?.createEnvSyncAdapter) {
280
283
  map.env = envModule.createEnvSyncAdapter(requiredEnvVars ?? []);
@@ -8,6 +8,7 @@ var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
10
10
  var _DialIcon = require("./DialIcon.js");
11
+ var _DialHyperspace = require("./DialHyperspace.js");
11
12
  var _allToolsRegistry = require("../allToolsRegistry.js");
12
13
  var _DialPagination = require("./DialPagination.js");
13
14
  var _dialUsageStore = require("./dialUsageStore.js");
@@ -24,6 +25,19 @@ var _jsxRuntime = require("react/jsx-runtime");
24
25
  // the component via useWindowDimensions — a module-scope Dimensions.get
25
26
  // snapshot goes stale when the window resizes after load (web/desktop).
26
27
  const BUTTON_SIZE = _floatingToolsCore.DIAL_BUTTON_SIZE;
28
+
29
+ /**
30
+ * The centre button's idle pulse, off by design.
31
+ *
32
+ * It is built as `Animated.loop(Animated.sequence([...]))`, and that pairing is
33
+ * NOT handed to the native driver even though every child sets
34
+ * `useNativeDriver: true` — so it ticked on the JS thread for as long as the
35
+ * dial stayed open, taxing every interaction launched from the dial.
36
+ *
37
+ * Re-enabling it means rewriting the loop so it genuinely runs natively;
38
+ * flipping this alone just puts the JS-thread cost back.
39
+ */
40
+ const DIAL_IDLE_PULSE = false;
27
41
  const ONBOARDING_STORAGE_KEY = "@react_buoy_settings_tooltip_shown";
28
42
  /** A non-interactive placeholder used to fill out the last dial page. */
29
43
  const createEmptySlot = slotIndex => ({
@@ -67,6 +81,11 @@ const DialDevTools = ({
67
81
  const circleSize = (0, _floatingToolsCore.getDialLayout)({
68
82
  screenWidth
69
83
  }).circleSize;
84
+ // The dial face shows the same backdrop the tools use (persisted shared
85
+ // selection). Hyperspace keeps its hand-tuned dial port below.
86
+ const {
87
+ id: backgroundId
88
+ } = (0, _sharedUi.useToolBackground)();
70
89
  const sizeStyles = (0, _react.useMemo)(() => ({
71
90
  parent: {
72
91
  width: circleSize,
@@ -153,16 +172,35 @@ const DialDevTools = ({
153
172
  id,
154
173
  name,
155
174
  slot,
156
- description
175
+ description,
176
+ icon,
177
+ color
157
178
  }) => ({
158
179
  id,
159
180
  name,
160
181
  slot,
161
- description
182
+ description,
183
+ // Settings rows fall back to the app's own icon when the modal has no
184
+ // hardcoded glyph for the tool.
185
+ icon,
186
+ color
162
187
  })), [apps]);
163
188
 
164
189
  // Animation tracking refs
165
190
  const pulseAnimationRef = (0, _react.useRef)(null);
191
+ const toggleSettingsModal = () => {
192
+ if (isSettingsModalOpen) {
193
+ setIsSettingsModalOpen(false);
194
+ return;
195
+ }
196
+ setIsSettingsModalOpen(true);
197
+ // Dismiss onboarding tooltip when user opens settings
198
+ if (showOnboardingTooltip && !onboardingDismissedRef.current) {
199
+ handleOnboardingDismiss();
200
+ }
201
+ // Also call external handler if provided
202
+ onSettingsPress?.();
203
+ };
166
204
 
167
205
  // Dial-eligible apps: everything except row-only tools. All of them are
168
206
  // shown — paginated across pages of MAX_DIAL_SLOTS — so there is no longer
@@ -410,6 +448,7 @@ const DialDevTools = ({
410
448
  })]).start();
411
449
 
412
450
  // Pulse animation - only continuous effect kept for performance
451
+ if (!DIAL_IDLE_PULSE) return;
413
452
  pulseAnimationRef.current = _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(pulseScale, {
414
453
  toValue: continuous.pulse.maxScale,
415
454
  duration: continuous.pulse.duration,
@@ -598,7 +637,9 @@ const DialDevTools = ({
598
637
  }]
599
638
  }]
600
639
  }, i))
601
- })]
640
+ }), backgroundId === "hyperspace" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_DialHyperspace.DialHyperspace, {
641
+ size: circleSize
642
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolBackground, {})]
602
643
  }), allDialIcons.filter(({
603
644
  page
604
645
  }) => page === safePage).map(({
@@ -633,26 +674,14 @@ const DialDevTools = ({
633
674
  style: styles.buttonBorder,
634
675
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
635
676
  style: [styles.button, pulseAnimatedStyle],
636
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
677
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
637
678
  accessibilityRole: "button",
638
679
  accessibilityLabel: isSettingsModalOpen ? "Close Settings" : "Open Dev Tools Settings",
639
- onPress: () => {
640
- if (isSettingsModalOpen) {
641
- // Close settings modal
642
- setIsSettingsModalOpen(false);
643
- } else {
644
- // Open internal settings modal
645
- setIsSettingsModalOpen(true);
646
- // Dismiss onboarding tooltip when user opens settings
647
- if (showOnboardingTooltip && !onboardingDismissedRef.current) {
648
- handleOnboardingDismiss();
649
- }
650
- // Also call external handler if provided
651
- if (onSettingsPress) {
652
- onSettingsPress();
653
- }
654
- }
655
- },
680
+ onPress: toggleSettingsModal
681
+ // Dims on touch-down so the press registers visually straight
682
+ // away, even though the panel only opens on release.
683
+ ,
684
+ activeOpacity: 0.5,
656
685
  style: styles.buttonPressable,
657
686
  children: isSettingsModalOpen ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
658
687
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DialHyperspace = void 0;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * DialHyperspace — the Hyperspace backdrop's lightspeed starfield, scaled
13
+ * down to live inside the open dial's face. Replaces DialSnow (kept next
14
+ * door for an easy swap back) so the dial matches the tool background.
15
+ *
16
+ * Same construction as the full preset (shared-ui
17
+ * `backgrounds/variants/hyperspace.tsx`, see its header for the derivation):
18
+ * the Skia original's per-frame `screen = plane/z` projection becomes scale
19
+ * keyframes on phase-offset shells — a layer scaled by 1/z moves every star
20
+ * outward on the true hyperbolic curve — with each shell's wrap snap hidden
21
+ * inside the fade the original applies to the first/last 6% of a star's
22
+ * life. Dial tuning: fewer stars, a slightly gentler cycle and lower alpha
23
+ * so the field reads behind the icons instead of competing with them.
24
+ * Stars are round crisp points, as in the original — never streaks.
25
+ *
26
+ * Cost while the dial is open: 1 animated node, ~48 static views — the same
27
+ * budget DialSnow ran. The dial is fixed-size (never user-resized), so
28
+ * sizing geometry from the passed circle size is correct here, and the
29
+ * circle's `overflow: "hidden"` clips the streaks.
30
+ */
31
+
32
+ const Z_SPAN = 0.94;
33
+ const MAX_SCALE = 6;
34
+ const CYCLE_MS = 2400;
35
+ const SHELLS = 3;
36
+ const STARS_PER_SHELL = 16;
37
+ /** Stride 4: angle, radius, size jitter, palette pick — fixed seeds so the
38
+ * field is identical every open. */
39
+ const SHELL_TABLES = Array.from({
40
+ length: SHELLS
41
+ }, (_, k) => (0, _sharedUi.scatter)(0x5747 ^ k * 0x9e37, STARS_PER_SHELL * 4));
42
+ const STAR_COLORS = ["#FFFFFF", "#B3D9FF", "#8CB3FF"];
43
+
44
+ /** The dial face draws 6 grid spokes, one every 60° starting at 0°. Star
45
+ * motion is radial (uniform scale about the centre), so a star whose angle
46
+ * sits near a spoke rides that line for its entire life. Remap the uniform
47
+ * angle pick into the clear wedge between spokes, leaving `SPOKE_GAP` of
48
+ * clearance centred on each line. */
49
+ const SPOKE_STEP = Math.PI / 3;
50
+ const SPOKE_GAP = 14 * Math.PI / 180;
51
+ function spokeClearAngle(u) {
52
+ const sector = Math.min(5, Math.floor(u * 6));
53
+ const t = u * 6 - sector;
54
+ return sector * SPOKE_STEP + SPOKE_GAP / 2 + t * (SPOKE_STEP - SPOKE_GAP);
55
+ }
56
+ function scaleAt(p) {
57
+ return Math.min(MAX_SCALE, 1 / (1 - p * Z_SPAN));
58
+ }
59
+ function opacityAt(p) {
60
+ let bright = 0.15 + 0.85 * p * p;
61
+ // The original's 6% fade at both ends of a star's life.
62
+ if (p < 0.06) bright *= p / 0.06;else if (p > 0.94) bright *= (1 - p) / 0.06;
63
+ // Dial tuning: never brighter than 0.7 — the icons own the foreground.
64
+ return Math.min(0.7, bright);
65
+ }
66
+
67
+ /** Interpolate stops for shell k (phase = (v + k/K) mod 1), the wrap point
68
+ * double-sampled 1e-4 apart inside the zero-opacity sliver. */
69
+ function shellStops(k) {
70
+ const offset = k / SHELLS;
71
+ const vs = [];
72
+ const SEGMENTS = 12;
73
+ for (let i = 0; i <= SEGMENTS; i++) vs.push(i / SEGMENTS);
74
+ const vWrap = (1 - offset) % 1;
75
+ if (vWrap > 1e-3 && vWrap < 1 - 1e-3) vs.push(vWrap - 1e-4, vWrap + 1e-4);
76
+ vs.sort((a, b) => a - b);
77
+ const input = [];
78
+ const scale = [];
79
+ const opacity = [];
80
+ for (const v of vs) {
81
+ // At the loop boundary the offset-0 shell's phase must read as END of
82
+ // life (p=1, max scale, zero opacity), not wrap to 0 — otherwise the
83
+ // last segment lerps scale 6→1 at visible opacity and the stars
84
+ // retract to the centre once per cycle.
85
+ const p = offset === 0 && v === 1 ? 1 : (v + offset) % 1;
86
+ input.push(v);
87
+ scale.push(scaleAt(p));
88
+ opacity.push(opacityAt(p));
89
+ }
90
+ return {
91
+ input,
92
+ scale,
93
+ opacity
94
+ };
95
+ }
96
+ const SHELL_STOPS = Array.from({
97
+ length: SHELLS
98
+ }, (_, k) => shellStops(k));
99
+ const DialHyperspace = exports.DialHyperspace = /*#__PURE__*/(0, _react.memo)(function DialHyperspace({
100
+ size
101
+ }) {
102
+ const drive = (0, _sharedUi.useLoop)(CYCLE_MS);
103
+ const c = size / 2;
104
+ const shells = [];
105
+ for (let k = 0; k < SHELLS; k++) {
106
+ const table = SHELL_TABLES[k];
107
+ const stops = SHELL_STOPS[k];
108
+ const stars = [];
109
+ for (let i = 0; i < STARS_PER_SHELL; i++) {
110
+ const ang = spokeClearAngle(table[i * 4]);
111
+ // sqrt-biased outward, inner 15% clear of the vanishing point. Offsets
112
+ // scale with the HALF-extent, as in the original's projection.
113
+ const rad = (0.15 + 0.85 * Math.sqrt(table[i * 4 + 1])) * 0.5 * c;
114
+ const color = STAR_COLORS[Math.floor(table[i * 4 + 3] * STAR_COLORS.length) % 3];
115
+ const x = c + Math.cos(ang) * rad;
116
+ const y = c + Math.sin(ang) * rad;
117
+ // Round crisp points, as in the original — the visible star is the
118
+ // sprite's lit core (~12.5% of the stamp), so it starts sub-pixel and
119
+ // only reaches ~2-3px before the fade-out kills it. A soft halo disc
120
+ // at α0.3 stands in for the sprite's blur.
121
+ const core = 0.6;
122
+ const halo = core * 2.4;
123
+ stars.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
124
+ style: {
125
+ position: "absolute",
126
+ left: x - halo / 2,
127
+ top: y - halo / 2,
128
+ width: halo,
129
+ height: halo,
130
+ borderRadius: halo / 2,
131
+ backgroundColor: (0, _sharedUi.withBgAlpha)(color, 0.3)
132
+ }
133
+ }, `${i}-h`), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
134
+ style: {
135
+ position: "absolute",
136
+ left: x - core / 2,
137
+ top: y - core / 2,
138
+ width: core,
139
+ height: core,
140
+ borderRadius: core / 2,
141
+ backgroundColor: color
142
+ }
143
+ }, i));
144
+ }
145
+ shells.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
146
+ style: {
147
+ position: "absolute",
148
+ left: 0,
149
+ top: 0,
150
+ width: size,
151
+ height: size,
152
+ opacity: drive.interpolate({
153
+ inputRange: stops.input,
154
+ outputRange: stops.opacity
155
+ }),
156
+ transform: [{
157
+ scale: drive.interpolate({
158
+ inputRange: stops.input,
159
+ outputRange: stops.scale
160
+ })
161
+ }]
162
+ },
163
+ pointerEvents: "none",
164
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RasterLayer, {
165
+ style: {
166
+ width: size,
167
+ height: size
168
+ },
169
+ raster: false,
170
+ children: stars
171
+ })
172
+ }, k));
173
+ }
174
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
175
+ children: shells
176
+ });
177
+ });
@@ -8,6 +8,34 @@ var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
10
10
  var _jsxRuntime = require("react/jsx-runtime");
11
+ /** Full names agents wait for (`waitFor: "Network"`), not the truncated glyph. */const DIAL_A11Y = {
12
+ network: "Network",
13
+ images: "Images",
14
+ "image-overlay": "Image Overlay",
15
+ assets: "Assets",
16
+ query: "React Query",
17
+ storage: "Storage",
18
+ env: "Env",
19
+ console: "Console",
20
+ events: "Events",
21
+ redux: "Redux",
22
+ zustand: "Zustand",
23
+ jotai: "Jotai",
24
+ "route-events": "Routes",
25
+ "time-machine": "Time Machine",
26
+ "highlight-updates": "Highlight Updates",
27
+ "debug-borders": "Debug Borders",
28
+ "perf-monitor": "Perf Monitor",
29
+ "js-top": "JS Top",
30
+ "js-top-modal": "JS Top",
31
+ impersonate: "Impersonate",
32
+ "perf-monitor-modal": "Perf Monitor"
33
+ };
34
+ function dialAccessibilityLabel(id, name) {
35
+ if (id && DIAL_A11Y[id]) return DIAL_A11Y[id];
36
+ return name;
37
+ }
38
+
11
39
  // The circle radius depends on the live window width and is computed inside
12
40
  // the component (must match DialDevTools' circle, which does the same).
13
41
  const VIEW_SIZE = _floatingToolsCore.DIAL_ICON_SIZE;
@@ -191,6 +219,9 @@ const DialIcon = ({
191
219
  onPressIn: handlePressIn,
192
220
  onPressOut: handlePressOut,
193
221
  style: styles.pressable,
222
+ testID: icon.id ? `buoy-dial-${icon.id}` : undefined,
223
+ accessibilityLabel: dialAccessibilityLabel(icon.id, icon.name),
224
+ accessibilityRole: "button",
194
225
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
195
226
  style: [styles.iconGradientBg, {
196
227
  backgroundColor: "rgba(0, 0, 0, 0.2)"
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DialSnow = void 0;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * DialSnow — pale particles drifting down inside the open dial's face, built
13
+ * from the shared background primitives (packages/shared/src/ui/backgrounds).
14
+ *
15
+ * Same performance contract as the backgrounds: the loop is native-driven
16
+ * (useLoop hands the whole animation to the UI thread once — zero JS work per
17
+ * frame), the flakes are one rasterized texture, and the scatter table is
18
+ * generated at module load. Cost while the dial is open: 1 animated node and
19
+ * ~24 static views, mounted only for the dial's lifetime.
20
+ *
21
+ * Rendered inside the circle's `overflow: "hidden"` background. The dial is
22
+ * fixed-size (never user-resized), so sizing geometry from the passed circle
23
+ * size is correct here.
24
+ */
25
+
26
+ /** Pale mint-ice flake tint — sits quietly on the dial's dark face. */const SNOW_COLOR = "#A8E0D2";
27
+
28
+ /** Stride 3: x, y, size — fixed seed so the flurry is identical every open. */
29
+ const SNOW_COUNT = 12;
30
+ const SNOW_TABLE = (0, _sharedUi.scatter)(0x5ea1, SNOW_COUNT * 3);
31
+
32
+ /**
33
+ * The two-copy tiling trick from the backdrops: everything drawn twice one
34
+ * height apart, translated a full height per loop, so the seam never shows.
35
+ * Sway is keyframed onto the SAME loop value.
36
+ */
37
+ const DialSnow = exports.DialSnow = /*#__PURE__*/(0, _react.memo)(function DialSnow({
38
+ size
39
+ }) {
40
+ const t = (0, _sharedUi.useLoop)(26000);
41
+ const translateY = t.interpolate({
42
+ inputRange: [0, 1],
43
+ outputRange: [0, size]
44
+ });
45
+ const translateX = t.interpolate({
46
+ inputRange: [0, 0.25, 0.5, 0.75, 1],
47
+ outputRange: [0, 5, 0, -5, 0]
48
+ });
49
+ const flakes = [];
50
+ for (let i = 0; i < SNOW_COUNT; i++) {
51
+ const x = SNOW_TABLE[i * 3] * size;
52
+ const y = SNOW_TABLE[i * 3 + 1] * size;
53
+ // Bigger flakes read nearer, so they get more alpha too.
54
+ const dia = 1.4 + SNOW_TABLE[i * 3 + 2] * 1.2;
55
+ const alpha = 0.28 + SNOW_TABLE[i * 3 + 2] * 0.18;
56
+ for (let copy = 0; copy < 2; copy++) {
57
+ flakes.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
58
+ style: {
59
+ position: "absolute",
60
+ left: x - dia / 2,
61
+ top: y + copy * size - dia / 2,
62
+ width: dia,
63
+ height: dia,
64
+ borderRadius: dia / 2,
65
+ backgroundColor: (0, _sharedUi.withBgAlpha)(SNOW_COLOR, alpha)
66
+ }
67
+ }, `${i}-${copy}`));
68
+ }
69
+ }
70
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
71
+ style: {
72
+ position: "absolute",
73
+ left: 0,
74
+ top: -size,
75
+ width: size,
76
+ height: size * 2,
77
+ transform: [{
78
+ translateY
79
+ }, {
80
+ translateX
81
+ }]
82
+ },
83
+ pointerEvents: "none",
84
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RasterLayer, {
85
+ style: {
86
+ width: size,
87
+ height: size * 2
88
+ },
89
+ children: flakes
90
+ })
91
+ });
92
+ });
@@ -15,4 +15,4 @@ exports.SDK_VERSION = void 0;
15
15
  * Drift is prevented by a test that fails when this disagrees with
16
16
  * package.json, so a version bump that forgets this file cannot ship.
17
17
  */
18
- const SDK_VERSION = exports.SDK_VERSION = "7.0.26";
18
+ const SDK_VERSION = exports.SDK_VERSION = "7.0.28";