@buoy-gg/shared-ui 7.0.22 → 7.0.23

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 (154) hide show
  1. package/lib/commonjs/JsModal.js +39 -16
  2. package/lib/commonjs/dataViewer/JsonStringNotice.js +59 -0
  3. package/lib/commonjs/dataViewer/ThemedSplitView.js +20 -1
  4. package/lib/commonjs/dataViewer/TimestampDeltaBar.js +96 -0
  5. package/lib/commonjs/dataViewer/index.js +52 -0
  6. package/lib/commonjs/dataViewer/jsonStringValue.js +94 -0
  7. package/lib/commonjs/dataViewer/timestampDiff.js +146 -0
  8. package/lib/commonjs/hooks/safe-area-impl.js +1 -1
  9. package/lib/commonjs/index.js +50 -1
  10. package/lib/commonjs/license/FeatureGate.js +43 -10
  11. package/lib/commonjs/license/FreeAccountPrompt.js +181 -0
  12. package/lib/commonjs/license/index.js +57 -42
  13. package/lib/commonjs/license/licenseModule.js +99 -0
  14. package/lib/commonjs/license/tierLimits.js +165 -0
  15. package/lib/commonjs/ui/backgrounds/BackgroundSwitcher.js +180 -0
  16. package/lib/commonjs/ui/backgrounds/README.md +115 -0
  17. package/lib/commonjs/ui/backgrounds/ToolBackground.js +74 -0
  18. package/lib/commonjs/ui/backgrounds/backgroundStore.js +116 -0
  19. package/lib/commonjs/ui/backgrounds/index.js +70 -0
  20. package/lib/commonjs/ui/backgrounds/primitives.js +333 -0
  21. package/lib/commonjs/ui/backgrounds/registry.js +112 -0
  22. package/lib/commonjs/ui/backgrounds/rng.js +72 -0
  23. package/lib/commonjs/ui/backgrounds/types.js +1 -0
  24. package/lib/commonjs/ui/backgrounds/variants/deepField.js +351 -0
  25. package/lib/commonjs/ui/backgrounds/variants/lattice.js +579 -0
  26. package/lib/commonjs/ui/backgrounds/variants/starfield.js +210 -0
  27. package/lib/commonjs/ui/index.js +15 -3
  28. package/lib/module/JsModal.js +39 -16
  29. package/lib/module/dataViewer/JsonStringNotice.js +54 -0
  30. package/lib/module/dataViewer/ThemedSplitView.js +20 -1
  31. package/lib/module/dataViewer/TimestampDeltaBar.js +91 -0
  32. package/lib/module/dataViewer/index.js +4 -0
  33. package/lib/module/dataViewer/jsonStringValue.js +89 -0
  34. package/lib/module/dataViewer/timestampDiff.js +139 -0
  35. package/lib/module/hooks/safe-area-impl.js +1 -1
  36. package/lib/module/index.js +1 -1
  37. package/lib/module/license/FeatureGate.js +43 -10
  38. package/lib/module/license/FreeAccountPrompt.js +176 -0
  39. package/lib/module/license/index.js +5 -40
  40. package/lib/module/license/licenseModule.js +92 -0
  41. package/lib/module/license/tierLimits.js +158 -0
  42. package/lib/module/ui/backgrounds/BackgroundSwitcher.js +176 -0
  43. package/lib/module/ui/backgrounds/README.md +115 -0
  44. package/lib/module/ui/backgrounds/ToolBackground.js +70 -0
  45. package/lib/module/ui/backgrounds/backgroundStore.js +111 -0
  46. package/lib/module/ui/backgrounds/index.js +9 -0
  47. package/lib/module/ui/backgrounds/primitives.js +328 -0
  48. package/lib/module/ui/backgrounds/registry.js +105 -0
  49. package/lib/module/ui/backgrounds/rng.js +65 -0
  50. package/lib/module/ui/backgrounds/types.js +1 -0
  51. package/lib/module/ui/backgrounds/variants/deepField.js +347 -0
  52. package/lib/module/ui/backgrounds/variants/lattice.js +575 -0
  53. package/lib/module/ui/backgrounds/variants/starfield.js +206 -0
  54. package/lib/module/ui/index.js +3 -0
  55. package/lib/typescript/commonjs/JsModal.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/dataViewer/JsonStringNotice.d.ts +6 -0
  57. package/lib/typescript/commonjs/dataViewer/JsonStringNotice.d.ts.map +1 -0
  58. package/lib/typescript/commonjs/dataViewer/ThemedSplitView.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/dataViewer/TimestampDeltaBar.d.ts +7 -0
  60. package/lib/typescript/commonjs/dataViewer/TimestampDeltaBar.d.ts.map +1 -0
  61. package/lib/typescript/commonjs/dataViewer/index.d.ts +6 -0
  62. package/lib/typescript/commonjs/dataViewer/index.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/dataViewer/jsonStringValue.d.ts +25 -0
  64. package/lib/typescript/commonjs/dataViewer/jsonStringValue.d.ts.map +1 -0
  65. package/lib/typescript/commonjs/dataViewer/timestampDiff.d.ts +55 -0
  66. package/lib/typescript/commonjs/dataViewer/timestampDiff.d.ts.map +1 -0
  67. package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
  68. package/lib/typescript/commonjs/index.d.ts +2 -2
  69. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  70. package/lib/typescript/commonjs/license/FeatureGate.d.ts +19 -3
  71. package/lib/typescript/commonjs/license/FeatureGate.d.ts.map +1 -1
  72. package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts +23 -0
  73. package/lib/typescript/commonjs/license/FreeAccountPrompt.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/license/index.d.ts +6 -15
  75. package/lib/typescript/commonjs/license/index.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/license/licenseModule.d.ts +55 -0
  77. package/lib/typescript/commonjs/license/licenseModule.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/license/tierLimits.d.ts +143 -0
  79. package/lib/typescript/commonjs/license/tierLimits.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/ui/backgrounds/BackgroundSwitcher.d.ts +13 -0
  81. package/lib/typescript/commonjs/ui/backgrounds/BackgroundSwitcher.d.ts.map +1 -0
  82. package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts +30 -0
  83. package/lib/typescript/commonjs/ui/backgrounds/ToolBackground.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/ui/backgrounds/backgroundStore.d.ts +43 -0
  85. package/lib/typescript/commonjs/ui/backgrounds/backgroundStore.d.ts.map +1 -0
  86. package/lib/typescript/commonjs/ui/backgrounds/index.d.ts +7 -0
  87. package/lib/typescript/commonjs/ui/backgrounds/index.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/ui/backgrounds/primitives.d.ts +106 -0
  89. package/lib/typescript/commonjs/ui/backgrounds/primitives.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts +28 -0
  91. package/lib/typescript/commonjs/ui/backgrounds/registry.d.ts.map +1 -0
  92. package/lib/typescript/commonjs/ui/backgrounds/rng.d.ts +27 -0
  93. package/lib/typescript/commonjs/ui/backgrounds/rng.d.ts.map +1 -0
  94. package/lib/typescript/commonjs/ui/backgrounds/types.d.ts +14 -0
  95. package/lib/typescript/commonjs/ui/backgrounds/types.d.ts.map +1 -0
  96. package/lib/typescript/commonjs/ui/backgrounds/variants/deepField.d.ts +65 -0
  97. package/lib/typescript/commonjs/ui/backgrounds/variants/deepField.d.ts.map +1 -0
  98. package/lib/typescript/commonjs/ui/backgrounds/variants/lattice.d.ts +33 -0
  99. package/lib/typescript/commonjs/ui/backgrounds/variants/lattice.d.ts.map +1 -0
  100. package/lib/typescript/commonjs/ui/backgrounds/variants/starfield.d.ts +12 -0
  101. package/lib/typescript/commonjs/ui/backgrounds/variants/starfield.d.ts.map +1 -0
  102. package/lib/typescript/commonjs/ui/index.d.ts +1 -0
  103. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  104. package/lib/typescript/module/JsModal.d.ts.map +1 -1
  105. package/lib/typescript/module/dataViewer/JsonStringNotice.d.ts +6 -0
  106. package/lib/typescript/module/dataViewer/JsonStringNotice.d.ts.map +1 -0
  107. package/lib/typescript/module/dataViewer/ThemedSplitView.d.ts.map +1 -1
  108. package/lib/typescript/module/dataViewer/TimestampDeltaBar.d.ts +7 -0
  109. package/lib/typescript/module/dataViewer/TimestampDeltaBar.d.ts.map +1 -0
  110. package/lib/typescript/module/dataViewer/index.d.ts +6 -0
  111. package/lib/typescript/module/dataViewer/index.d.ts.map +1 -1
  112. package/lib/typescript/module/dataViewer/jsonStringValue.d.ts +25 -0
  113. package/lib/typescript/module/dataViewer/jsonStringValue.d.ts.map +1 -0
  114. package/lib/typescript/module/dataViewer/timestampDiff.d.ts +55 -0
  115. package/lib/typescript/module/dataViewer/timestampDiff.d.ts.map +1 -0
  116. package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
  117. package/lib/typescript/module/index.d.ts +2 -2
  118. package/lib/typescript/module/index.d.ts.map +1 -1
  119. package/lib/typescript/module/license/FeatureGate.d.ts +19 -3
  120. package/lib/typescript/module/license/FeatureGate.d.ts.map +1 -1
  121. package/lib/typescript/module/license/FreeAccountPrompt.d.ts +23 -0
  122. package/lib/typescript/module/license/FreeAccountPrompt.d.ts.map +1 -0
  123. package/lib/typescript/module/license/index.d.ts +6 -15
  124. package/lib/typescript/module/license/index.d.ts.map +1 -1
  125. package/lib/typescript/module/license/licenseModule.d.ts +55 -0
  126. package/lib/typescript/module/license/licenseModule.d.ts.map +1 -0
  127. package/lib/typescript/module/license/tierLimits.d.ts +143 -0
  128. package/lib/typescript/module/license/tierLimits.d.ts.map +1 -0
  129. package/lib/typescript/module/ui/backgrounds/BackgroundSwitcher.d.ts +13 -0
  130. package/lib/typescript/module/ui/backgrounds/BackgroundSwitcher.d.ts.map +1 -0
  131. package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts +30 -0
  132. package/lib/typescript/module/ui/backgrounds/ToolBackground.d.ts.map +1 -0
  133. package/lib/typescript/module/ui/backgrounds/backgroundStore.d.ts +43 -0
  134. package/lib/typescript/module/ui/backgrounds/backgroundStore.d.ts.map +1 -0
  135. package/lib/typescript/module/ui/backgrounds/index.d.ts +7 -0
  136. package/lib/typescript/module/ui/backgrounds/index.d.ts.map +1 -0
  137. package/lib/typescript/module/ui/backgrounds/primitives.d.ts +106 -0
  138. package/lib/typescript/module/ui/backgrounds/primitives.d.ts.map +1 -0
  139. package/lib/typescript/module/ui/backgrounds/registry.d.ts +28 -0
  140. package/lib/typescript/module/ui/backgrounds/registry.d.ts.map +1 -0
  141. package/lib/typescript/module/ui/backgrounds/rng.d.ts +27 -0
  142. package/lib/typescript/module/ui/backgrounds/rng.d.ts.map +1 -0
  143. package/lib/typescript/module/ui/backgrounds/types.d.ts +14 -0
  144. package/lib/typescript/module/ui/backgrounds/types.d.ts.map +1 -0
  145. package/lib/typescript/module/ui/backgrounds/variants/deepField.d.ts +65 -0
  146. package/lib/typescript/module/ui/backgrounds/variants/deepField.d.ts.map +1 -0
  147. package/lib/typescript/module/ui/backgrounds/variants/lattice.d.ts +33 -0
  148. package/lib/typescript/module/ui/backgrounds/variants/lattice.d.ts.map +1 -0
  149. package/lib/typescript/module/ui/backgrounds/variants/starfield.d.ts +12 -0
  150. package/lib/typescript/module/ui/backgrounds/variants/starfield.d.ts.map +1 -0
  151. package/lib/typescript/module/ui/index.d.ts +1 -0
  152. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  153. package/package.json +4 -4
  154. package/scripts/detect-safe-area.js +0 -20
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Starfield = void 0;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _absoluteFill = require("../../../utils/absoluteFill.js");
10
+ var _primitives = require("../primitives.js");
11
+ var _rng = require("../rng.js");
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ /**
14
+ * Starfield — the RN-primitive port of the Everlights parallax-drift starfield.
15
+ *
16
+ * The original is a GPU-instanced Skia `<Atlas>` with a per-star worklet; Buoy
17
+ * can't depend on Skia, so the depth continuum is quantised into five discrete
18
+ * bands. Each band is one rasterized layer of dots sliding at its own speed —
19
+ * five native-driven loops instead of 160 per-frame transforms — and the eye
20
+ * reads five parallax speeds as depth just as well as it reads a continuum.
21
+ */
22
+
23
+ /**
24
+ * Depth bands, far → near. `z` drives both size and speed (drift/sec at z=1 is
25
+ * 0.016 of the width, matching the original), `count` thins out as stars get
26
+ * closer so the fast, big ones stay sparse.
27
+ */
28
+ const BANDS = [
29
+ // dia is the star's core in px; halo is how many core-widths the bloom
30
+ // spreads (0 = no bloom, which is what keeps the far bands cheap).
31
+ {
32
+ z: 1.0,
33
+ count: 38,
34
+ dia: 1.4,
35
+ halo: 0,
36
+ color: "#7C93B5",
37
+ alpha: 0.55
38
+ }, {
39
+ z: 0.78,
40
+ count: 26,
41
+ dia: 1.9,
42
+ halo: 0,
43
+ color: "#A6BBD8",
44
+ alpha: 0.65
45
+ }, {
46
+ z: 0.6,
47
+ count: 18,
48
+ dia: 2.4,
49
+ halo: 0,
50
+ color: "#C9D9F0",
51
+ alpha: 0.8
52
+ }, {
53
+ z: 0.45,
54
+ count: 11,
55
+ dia: 3.1,
56
+ halo: 0,
57
+ color: "#E4EEFC",
58
+ alpha: 0.92
59
+ },
60
+ // Only the closest band gets a bloom: 6 stars x 3 extra discs is 36 views,
61
+ // and it is the entire difference between "dots" and "stars".
62
+ {
63
+ z: 0.3,
64
+ count: 6,
65
+ dia: 4.2,
66
+ halo: 4.4,
67
+ color: "#FFFFFF",
68
+ alpha: 1
69
+ }];
70
+ const DRIFT = 0.016; // fraction of width travelled per second at z=1
71
+
72
+ /** Pre-scattered x/y per band, generated once at module load. */
73
+ const BAND_TABLES = BANDS.map((b, i) => ({
74
+ xs: (0, _rng.scatter)(0x5747 + i * 977, b.count),
75
+ ys: (0, _rng.scatter)(0x91c3 + i * 613, b.count)
76
+ }));
77
+
78
+ /**
79
+ * The bloom around a close star. Three nested discs rather than a `RadialGlow`,
80
+ * because this one is drawn per star and lives INSIDE the band layer — which is
81
+ * the point: the halo travels with its star instead of sitting still while the
82
+ * star drifts off it.
83
+ */
84
+ function haloDiscs(cx, cy, core, spread, color, key) {
85
+ const out = [];
86
+ const steps = [{
87
+ mul: spread,
88
+ alpha: 0.05
89
+ }, {
90
+ mul: spread * 0.62,
91
+ alpha: 0.09
92
+ }, {
93
+ mul: spread * 0.34,
94
+ alpha: 0.16
95
+ }];
96
+ for (let i = 0; i < steps.length; i++) {
97
+ const d = core * steps[i].mul;
98
+ out.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
99
+ style: {
100
+ position: "absolute",
101
+ left: cx - d / 2,
102
+ top: cy - d / 2,
103
+ width: d,
104
+ height: d,
105
+ borderRadius: d / 2,
106
+ backgroundColor: (0, _rng.withAlpha)(color, steps[i].alpha)
107
+ }
108
+ }, `${key}-h${i}`));
109
+ }
110
+ return out;
111
+ }
112
+ const StarBand = /*#__PURE__*/(0, _react.memo)(function StarBand({
113
+ index,
114
+ width,
115
+ height,
116
+ animated
117
+ }) {
118
+ const band = BANDS[index];
119
+ const table = BAND_TABLES[index];
120
+ // One full width of travel takes z / DRIFT seconds — nearer (smaller z) is
121
+ // faster, which is the whole parallax effect.
122
+ const drift = (0, _primitives.useLoop)(band.z / DRIFT * 1000, animated);
123
+ const translateX = drift.interpolate({
124
+ inputRange: [0, 1],
125
+ outputRange: [0, -width]
126
+ });
127
+ const dots = [];
128
+ for (let i = 0; i < band.count; i++) {
129
+ const x = table.xs[i] * width;
130
+ const y = table.ys[i] * height;
131
+ // Each star is drawn twice, one width apart, so the layer tiles seamlessly
132
+ // when the loop snaps back to 0.
133
+ for (let copy = 0; copy < 2; copy++) {
134
+ const cx = x + copy * width;
135
+ const key = `${i}-${copy}`;
136
+ if (band.halo > 0) {
137
+ dots.push(...haloDiscs(cx, y, band.dia, band.halo, band.color, key));
138
+ }
139
+ dots.push(/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
140
+ style: {
141
+ position: "absolute",
142
+ left: cx - band.dia / 2,
143
+ top: y - band.dia / 2,
144
+ width: band.dia,
145
+ height: band.dia,
146
+ borderRadius: band.dia / 2,
147
+ backgroundColor: (0, _rng.withAlpha)(band.color, band.alpha)
148
+ }
149
+ }, key));
150
+ }
151
+ }
152
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
153
+ style: [{
154
+ position: "absolute",
155
+ left: 0,
156
+ top: 0,
157
+ width: width * 2,
158
+ height
159
+ }, {
160
+ transform: [{
161
+ translateX
162
+ }]
163
+ }],
164
+ pointerEvents: "none",
165
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.RasterLayer, {
166
+ style: {
167
+ width: width * 2,
168
+ height
169
+ },
170
+ children: dots
171
+ })
172
+ });
173
+ });
174
+ const Starfield = exports.Starfield = /*#__PURE__*/(0, _react.memo)(function Starfield(props) {
175
+ // A single slow breath over the whole field. One node, and it's what stops
176
+ // the sky looking like a printed dot pattern now that the per-star twinkle
177
+ // from the Skia original isn't affordable.
178
+ const shimmer = (0, _primitives.usePulse)(5200, props.animated);
179
+ const opacity = shimmer.interpolate({
180
+ inputRange: [0, 1],
181
+ outputRange: [0.82, 1]
182
+ });
183
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
184
+ style: _absoluteFill.absoluteFill,
185
+ pointerEvents: "none",
186
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Base, {
187
+ color: "#05060A"
188
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.RadialGlow, {
189
+ size: props.width * 1.6,
190
+ color: "#1E3A8A",
191
+ alpha: 0.2,
192
+ rings: 8,
193
+ falloff: 2,
194
+ style: {
195
+ position: "absolute",
196
+ left: -props.width * 0.3,
197
+ top: -props.width * 0.9
198
+ }
199
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
200
+ style: [_absoluteFill.absoluteFill, {
201
+ opacity
202
+ }],
203
+ pointerEvents: "none",
204
+ children: BANDS.map((_, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(StarBand, {
205
+ index: i,
206
+ ...props
207
+ }, i))
208
+ })]
209
+ });
210
+ });
@@ -31,9 +31,7 @@ Object.keys(_index).forEach(function (key) {
31
31
  }
32
32
  });
33
33
  });
34
- var _toolTheme = require("./toolTheme.js");
35
- var _CyberpunkSectionButton = require("./console/CyberpunkSectionButton.js");
36
- var _index2 = require("./gameUI/index.js");
34
+ var _index2 = require("./backgrounds/index.js");
37
35
  Object.keys(_index2).forEach(function (key) {
38
36
  if (key === "default" || key === "__esModule") return;
39
37
  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
@@ -44,4 +42,18 @@ Object.keys(_index2).forEach(function (key) {
44
42
  return _index2[key];
45
43
  }
46
44
  });
45
+ });
46
+ var _toolTheme = require("./toolTheme.js");
47
+ var _CyberpunkSectionButton = require("./console/CyberpunkSectionButton.js");
48
+ var _index3 = require("./gameUI/index.js");
49
+ Object.keys(_index3).forEach(function (key) {
50
+ if (key === "default" || key === "__esModule") return;
51
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
52
+ if (key in exports && exports[key] === _index3[key]) return;
53
+ Object.defineProperty(exports, key, {
54
+ enumerable: true,
55
+ get: function () {
56
+ return _index3[key];
57
+ }
58
+ });
47
59
  });
@@ -693,6 +693,14 @@ const JsModalComponent = ({
693
693
  } else {
694
694
  // Floating mode entrance - simple fade without scale pop
695
695
  floatingScale.setValue(1); // Set scale to 1 directly, no animation
696
+ // Minimizing animated the window to the dock icon's corner. Put it
697
+ // back where it actually lives, or reopening leaves it parked
698
+ // off-screen — `initialModalState` only covers the dock's restore
699
+ // path, and the dial opens the same tool without one.
700
+ floatingPosition.setValue({
701
+ x: currentDimensionsRef.current.left,
702
+ y: currentDimensionsRef.current.top
703
+ });
696
704
  openAnimation = Animated.timing(visibilityProgress, {
697
705
  toValue: 1,
698
706
  duration: 200,
@@ -741,7 +749,7 @@ const JsModalComponent = ({
741
749
  // Stopped close animation
742
750
  }
743
751
  };
744
- }, [visible, mode, visibilityProgress, bottomSheetTranslateY, floatingScale, externalAnimatedHeight]); // Removed initialHeight to prevent animation restarts on height changes
752
+ }, [visible, mode, visibilityProgress, bottomSheetTranslateY, floatingScale, floatingPosition, externalAnimatedHeight]); // Removed initialHeight to prevent animation restarts on height changes
745
753
 
746
754
  // ============================================================================
747
755
  // OPTIMIZED PAN RESPONDER: Bottom Sheet Resize
@@ -1111,22 +1119,18 @@ const JsModalComponent = ({
1111
1119
  // RENDER: Modal UI with transform-based animations
1112
1120
  // ============================================================================
1113
1121
 
1114
- // When not visible, render off-screen to keep children mounted (preserves state/listeners)
1115
- // This is similar to how React Navigation keeps screens mounted
1116
- if (!visible) {
1117
- return /*#__PURE__*/_jsx(View, {
1118
- style: styles.hiddenContainer,
1119
- pointerEvents: "none",
1120
- accessibilityElementsHidden: true,
1121
- importantForAccessibility: "no-hide-descendants",
1122
- children: children
1123
- });
1124
- }
1122
+ // When not visible the SAME tree is rendered, just parked off-screen. It has
1123
+ // to be the same tree: React reconciles by position, so a separate "hidden"
1124
+ // branch would unmount and rebuild every child on each minimize AND each
1125
+ // restore — which is the whole cost minimizing is supposed to avoid.
1125
1126
 
1126
1127
  // Render floating mode
1127
1128
  if (mode === "floating") {
1128
1129
  return /*#__PURE__*/_jsxs(Animated.View, {
1129
1130
  nativeID: "jsmodal-root",
1131
+ pointerEvents: visible ? "auto" : "none",
1132
+ accessibilityElementsHidden: !visible,
1133
+ importantForAccessibility: visible ? "auto" : "no-hide-descendants",
1130
1134
  style: [styles.floatingModal, {
1131
1135
  width: dimensions.width,
1132
1136
  // Use state dimensions for real-time updates
@@ -1140,7 +1144,7 @@ const JsModalComponent = ({
1140
1144
  ...(zIndex !== undefined && {
1141
1145
  zIndex
1142
1146
  })
1143
- }, (isDragging || isResizing) && styles.floatingModalDragging, customStyles.container],
1147
+ }, (isDragging || isResizing) && styles.floatingModalDragging, customStyles.container, !visible && styles.parkedOffscreen],
1144
1148
  children: [/*#__PURE__*/_jsx(DraggableHeader, {
1145
1149
  position: floatingPosition,
1146
1150
  onDragStart: handleFloatingDragStart,
@@ -1237,10 +1241,16 @@ const JsModalComponent = ({
1237
1241
 
1238
1242
  // Render bottom sheet mode with proper height animation
1239
1243
  return /*#__PURE__*/_jsx(View, {
1240
- style: [styles.fullScreenContainer, zIndex !== undefined && {
1244
+ style: [
1245
+ // Same box either way — parking it off-screen instead of resizing it
1246
+ // keeps the sheet's layout intact, so coming back is a translate and
1247
+ // not a full relayout.
1248
+ visible ? styles.fullScreenContainer : styles.parkedFullScreen, visible && zIndex !== undefined && {
1241
1249
  zIndex
1242
1250
  }],
1243
- pointerEvents: "box-none",
1251
+ pointerEvents: visible ? "box-none" : "none",
1252
+ accessibilityElementsHidden: !visible,
1253
+ importantForAccessibility: visible ? "auto" : "no-hide-descendants",
1244
1254
  children: /*#__PURE__*/_jsx(Animated.View, {
1245
1255
  nativeID: "jsmodal-root",
1246
1256
  style: [styles.bottomSheetWrapper, {
@@ -1336,10 +1346,23 @@ const styles = StyleSheet.create({
1336
1346
  shadowRadius: 12,
1337
1347
  elevation: 20
1338
1348
  },
1339
- hiddenContainer: {
1349
+ /** Off-screen park for a minimized floating modal — same box, moved away. */
1350
+ parkedOffscreen: {
1351
+ left: -9999,
1352
+ top: -9999,
1353
+ opacity: 0
1354
+ },
1355
+ /**
1356
+ * Off-screen park for a minimized bottom sheet. Screen-sized on purpose:
1357
+ * `fullScreenContainer` is an absolute fill, and shrinking it here would
1358
+ * relayout the whole sheet on every minimize/restore.
1359
+ */
1360
+ parkedFullScreen: {
1340
1361
  position: "absolute",
1341
1362
  left: -9999,
1342
1363
  top: -9999,
1364
+ width: SCREEN.width,
1365
+ height: SCREEN.height,
1343
1366
  opacity: 0
1344
1367
  },
1345
1368
  floatingModal: {
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Says out loud that the diff below is showing a decoded value.
5
+ *
6
+ * Not decoration: the store holds a STRING, and the rows underneath show an
7
+ * object or an array. Without this line the viewer would be misreporting the
8
+ * type of what is stored — see jsonStringValue.ts.
9
+ */
10
+ import { memo } from "react";
11
+ import { View, Text, StyleSheet } from "react-native";
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ export const JsonStringNotice = /*#__PURE__*/memo(function JsonStringNotice({
14
+ visible,
15
+ theme
16
+ }) {
17
+ if (!visible) return null;
18
+ return /*#__PURE__*/_jsxs(View, {
19
+ style: [styles.container, {
20
+ backgroundColor: theme.summaryBackground,
21
+ borderTopColor: theme.borderColor
22
+ }],
23
+ children: [/*#__PURE__*/_jsx(Text, {
24
+ style: [styles.glyph, {
25
+ color: theme.lineNumberText
26
+ }],
27
+ children: "{ }"
28
+ }), /*#__PURE__*/_jsx(Text, {
29
+ style: [styles.text, {
30
+ color: theme.lineNumberText
31
+ }],
32
+ children: "Stored as a JSON string \u2014 decoded below so the change is readable."
33
+ })]
34
+ });
35
+ });
36
+ const styles = StyleSheet.create({
37
+ container: {
38
+ flexDirection: "row",
39
+ alignItems: "center",
40
+ gap: 7,
41
+ borderTopWidth: StyleSheet.hairlineWidth,
42
+ paddingHorizontal: 10,
43
+ paddingVertical: 6
44
+ },
45
+ glyph: {
46
+ fontSize: 11,
47
+ fontWeight: "700"
48
+ },
49
+ text: {
50
+ flex: 1,
51
+ fontSize: 11,
52
+ lineHeight: 14
53
+ }
54
+ });
@@ -14,6 +14,10 @@ import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } fro
14
14
  import { View, Text, FlatList, StyleSheet, TouchableOpacity } from "react-native";
15
15
  import { computeLineDiff, DiffType } from "./lineDiff.js";
16
16
  import { DiffSummary } from "./DiffSummary.js";
17
+ import { TimestampDeltaBar } from "./TimestampDeltaBar.js";
18
+ import { describeTimestampDeltas } from "./timestampDiff.js";
19
+ import { JsonStringNotice } from "./JsonStringNotice.js";
20
+ import { decodeJsonStringPair } from "./jsonStringValue.js";
17
21
 
18
22
  /** Rendering options (the shape storage's DiffOptionsPanel edits). */
19
23
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -42,7 +46,12 @@ export function ThemedSplitView({
42
46
  showDiffOnly: options.showDiffOnly,
43
47
  contextLines: options.contextLines
44
48
  }), [options.compareMethod, options.disableWordDiff, options.showDiffOnly, options.contextLines]);
45
- const lineDiffs = useMemo(() => computeLineDiff(oldValue, newValue, diffComputeOptions), [oldValue, newValue, diffComputeOptions]);
49
+
50
+ // A JSON-encoded string is diffed decoded, or its one real change reads as
51
+ // one unreadable escaped line. Everything downstream — the line diff, the
52
+ // timestamp scan — works off the decoded pair.
53
+ const pair = useMemo(() => decodeJsonStringPair(oldValue, newValue), [oldValue, newValue]);
54
+ const lineDiffs = useMemo(() => computeLineDiff(pair.left, pair.right, diffComputeOptions), [pair, diffComputeOptions]);
46
55
 
47
56
  // One pass for the summary instead of three .filter() calls per render.
48
57
  const summary = useMemo(() => {
@@ -59,6 +68,10 @@ export function ThemedSplitView({
59
68
  };
60
69
  }, [lineDiffs]);
61
70
 
71
+ // Detection walks at most one object level, but it runs on every render of
72
+ // a diff that may hold thousands of lines — keep it on the value identities.
73
+ const timestampDeltas = useMemo(() => describeTimestampDeltas(pair.left, pair.right), [pair]);
74
+
62
75
  // Auto-collapse runs of unchanged lines that aren't near a change. Keeps
63
76
  // CONTEXT_LINES on either side of every change visible (GitHub-style), folds
64
77
  // the rest into a tappable header.
@@ -147,6 +160,12 @@ export function ThemedSplitView({
147
160
  removed: summary.removed,
148
161
  modified: summary.modified,
149
162
  theme: theme
163
+ }), /*#__PURE__*/_jsx(JsonStringNotice, {
164
+ visible: pair.decoded,
165
+ theme: theme
166
+ }), /*#__PURE__*/_jsx(TimestampDeltaBar, {
167
+ deltas: timestampDeltas,
168
+ theme: theme
150
169
  }), lineDiffs.length === 0 ? /*#__PURE__*/_jsx(View, {
151
170
  style: dynamicStyles.emptyState,
152
171
  children: /*#__PURE__*/_jsx(Text, {
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * TimestampDeltaBar — reads out the timestamp pairs in a diff.
5
+ *
6
+ * Sits under the diff summary and translates `1787085897165 → 1787012175875`
7
+ * into "20h 29m earlier", with both instants spelled out. The raw values stay
8
+ * on the rows below: this annotates the diff, it never replaces it.
9
+ *
10
+ * Renders nothing when no side of the diff is confidently a timestamp — see
11
+ * timestampDiff.ts for how narrow "confidently" is.
12
+ */
13
+ import { memo } from "react";
14
+ import { View, Text, StyleSheet } from "react-native";
15
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
+ export const TimestampDeltaBar = /*#__PURE__*/memo(function TimestampDeltaBar({
17
+ deltas,
18
+ theme
19
+ }) {
20
+ if (deltas.length === 0) return null;
21
+ return /*#__PURE__*/_jsx(View, {
22
+ style: [styles.container, {
23
+ backgroundColor: theme.summaryBackground,
24
+ borderTopColor: theme.borderColor
25
+ }],
26
+ children: deltas.map(delta => /*#__PURE__*/_jsxs(View, {
27
+ style: styles.row,
28
+ children: [/*#__PURE__*/_jsx(Text, {
29
+ style: [styles.glyph, {
30
+ color: theme.summaryModifiedText
31
+ }],
32
+ children: "\u25F7"
33
+ }), /*#__PURE__*/_jsxs(View, {
34
+ style: styles.text,
35
+ children: [/*#__PURE__*/_jsxs(Text, {
36
+ style: styles.headline,
37
+ children: [delta.path ? /*#__PURE__*/_jsxs(Text, {
38
+ style: [styles.path, {
39
+ color: theme.lineNumberText
40
+ }],
41
+ children: [delta.path, " "]
42
+ }) : null, /*#__PURE__*/_jsx(Text, {
43
+ style: {
44
+ color: theme.summaryModifiedText
45
+ },
46
+ children: delta.deltaLabel
47
+ })]
48
+ }), /*#__PURE__*/_jsxs(Text, {
49
+ style: [styles.instants, {
50
+ color: theme.lineNumberText
51
+ }],
52
+ children: [delta.fromLabel, " \u2192 ", delta.toLabel]
53
+ })]
54
+ })]
55
+ }, delta.path ?? "__value__"))
56
+ });
57
+ });
58
+ const styles = StyleSheet.create({
59
+ container: {
60
+ borderTopWidth: StyleSheet.hairlineWidth,
61
+ paddingHorizontal: 10,
62
+ paddingVertical: 7,
63
+ gap: 6
64
+ },
65
+ row: {
66
+ flexDirection: "row",
67
+ alignItems: "flex-start",
68
+ gap: 7
69
+ },
70
+ glyph: {
71
+ fontSize: 12,
72
+ lineHeight: 15
73
+ },
74
+ text: {
75
+ flex: 1,
76
+ minWidth: 0
77
+ },
78
+ headline: {
79
+ fontSize: 12,
80
+ fontWeight: "600",
81
+ lineHeight: 15
82
+ },
83
+ path: {
84
+ fontWeight: "400"
85
+ },
86
+ instants: {
87
+ fontSize: 11,
88
+ marginTop: 1,
89
+ fontVariant: ["tabular-nums"]
90
+ }
91
+ });
@@ -33,6 +33,10 @@ export { DataTreeActionDock } from "./DataTreeActionDock.js";
33
33
  // Diff viewer components
34
34
  export { SplitDiffViewer } from "./SplitDiffViewer.js";
35
35
  export { DiffSummary } from "./DiffSummary.js";
36
+ export { TimestampDeltaBar } from "./TimestampDeltaBar.js";
37
+ export { JsonStringNotice } from "./JsonStringNotice.js";
38
+ export { decodeJsonStringPair, parseJsonContainer } from "./jsonStringValue.js";
39
+ export { detectTimestamp, describeTimestampDeltas, formatDuration, formatInstant } from "./timestampDiff.js";
36
40
  // Diff utilities and themes
37
41
  export { computeLineDiff, DiffType, ABSENT_VALUE, isAbsent } from "./lineDiff.js";
38
42
  export { diffThemes, gitClassicTheme, devToolsDefaultTheme } from "./diffThemes.js";
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * JSON-encoded string values in diffs.
5
+ *
6
+ * Every device storage backend stores STRINGS, so apps write
7
+ * `JSON.stringify(["pikachu"])` and the stored value is the six-character
8
+ * string `["pikachu"]`. Rendering that through the diff's own
9
+ * `JSON.stringify` escapes it a second time and you get `"[\"pikachu\"]"` on
10
+ * one unreadable line — and a real change (one entry added to a list of
11
+ * twenty) becomes a single modified line you can't read.
12
+ *
13
+ * So: when a side is a JSON-encoded OBJECT OR ARRAY, diff the decoded value
14
+ * and say so on screen. The label is the whole contract — the store really
15
+ * does hold a string, and a viewer that quietly showed an array without
16
+ * mentioning it would be lying about the shape of the data.
17
+ *
18
+ * Scalars are deliberately NOT decoded. Turning the stored string `"5"` into
19
+ * the number 5, or `"true"` into a boolean, changes the apparent type while
20
+ * adding no readability — the exact misrepresentation this is trying to
21
+ * avoid.
22
+ */
23
+ import { isAbsent } from "./lineDiff.js";
24
+
25
+ /**
26
+ * Parse a string that encodes a JSON object or array. Returns a wrapper so a
27
+ * successfully decoded `null`-ish payload is still distinguishable from "not
28
+ * decodable" (the prefix check makes that unreachable today, but callers
29
+ * shouldn't have to know that).
30
+ */
31
+ export function parseJsonContainer(value) {
32
+ if (typeof value !== "string") return null;
33
+ const trimmed = value.trim();
34
+ // Cheap gate before JSON.parse: only containers qualify, and this keeps a
35
+ // try/catch off the path for every ordinary string in a diff.
36
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
37
+ try {
38
+ const parsed = JSON.parse(trimmed);
39
+ if (typeof parsed !== "object" || parsed === null) return null;
40
+ return {
41
+ value: parsed
42
+ };
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ /** A side that carries no value at all — an add or a delete, not a change. */
48
+ function isMissing(value) {
49
+ return isAbsent(value) || value === undefined || value === null;
50
+ }
51
+
52
+ /**
53
+ * Decode both sides of a diff when they are JSON-encoded containers.
54
+ *
55
+ * One side alone is decoded ONLY when the other side is missing entirely
56
+ * (a key being added or removed). A JSON string on one side and an ordinary
57
+ * value on the other is a change of shape, and decoding just one half would
58
+ * present it as something it isn't.
59
+ */
60
+ export function decodeJsonStringPair(left, right) {
61
+ const decodedLeft = parseJsonContainer(left);
62
+ const decodedRight = parseJsonContainer(right);
63
+ if (decodedLeft && decodedRight) {
64
+ return {
65
+ left: decodedLeft.value,
66
+ right: decodedRight.value,
67
+ decoded: true
68
+ };
69
+ }
70
+ if (decodedLeft && isMissing(right)) {
71
+ return {
72
+ left: decodedLeft.value,
73
+ right,
74
+ decoded: true
75
+ };
76
+ }
77
+ if (decodedRight && isMissing(left)) {
78
+ return {
79
+ left,
80
+ right: decodedRight.value,
81
+ decoded: true
82
+ };
83
+ }
84
+ return {
85
+ left,
86
+ right,
87
+ decoded: false
88
+ };
89
+ }