@tastic/hud 0.2.0 → 0.4.0

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.
package/dist/index.js CHANGED
@@ -28,22 +28,22 @@ __export(TriggerGauge_exports, {
28
28
  function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, dashWidth, startDeg = DEFAULT_START_DEG, endDeg = startDeg }) {
29
29
  const totalDurationMs = MOUNT_DURATION_MS + Math.max(0, segments - 1) * MOUNT_STAGGER_MS;
30
30
  const mountProgress = (0, import_react_native_reanimated.useSharedValue)(0);
31
- (0, import_react3.useLayoutEffect)(() => {
31
+ (0, import_react4.useLayoutEffect)(() => {
32
32
  mountProgress.value = (0, import_react_native_reanimated.withTiming)(1, { duration: totalDurationMs, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
33
33
  }, []);
34
34
  const rawSweepDeg = ((endDeg - startDeg) % 360 + 360) % 360;
35
35
  const totalSweepDeg = rawSweepDeg === 0 ? 360 : rawSweepDeg;
36
36
  const step = segments > 0 ? totalSweepDeg / segments : 0;
37
- const centerDeg = (0, import_react3.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
37
+ const centerDeg = (0, import_react4.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
38
38
  const singleSelect = litIndices.length === 1;
39
39
  const angleDeg = (0, import_react_native_reanimated.useSharedValue)(centerDeg(litIndices[0] ?? 0));
40
40
  const litIndicesKey = [...litIndices].sort((a, b) => a - b).join(",");
41
- const [transitionFrom, setTransitionFrom] = (0, import_react3.useState)(litIndices);
41
+ const [transitionFrom, setTransitionFrom] = (0, import_react4.useState)(litIndices);
42
42
  const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
43
- const lastKeyRef = (0, import_react3.useRef)(litIndicesKey);
44
- const lastIndicesRef = (0, import_react3.useRef)(litIndices);
43
+ const lastKeyRef = (0, import_react4.useRef)(litIndicesKey);
44
+ const lastIndicesRef = (0, import_react4.useRef)(litIndices);
45
45
  const selectionProgress = (0, import_react_native_reanimated.useSharedValue)(1);
46
- (0, import_react3.useLayoutEffect)(() => {
46
+ (0, import_react4.useLayoutEffect)(() => {
47
47
  if (litIndicesKey !== lastKeyRef.current) {
48
48
  if (singleSelect && lastIndicesRef.current.length === 1) {
49
49
  angleDeg.value = (0, import_react_native_reanimated.withTiming)(centerDeg(litIndices[0]), { duration: SELECTION_TRANSITION_DURATION_MS, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
@@ -120,21 +120,21 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
120
120
  height: canvasSize,
121
121
  width: canvasSize
122
122
  };
123
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.View, { style: [styles5.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
124
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
125
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
123
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native12.View, { style: [styles9.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
126
126
  ] }) });
127
127
  }
128
- var import_react_native_skia, import_core3, import_react3, import_react_native8, import_react_native_reanimated, import_jsx_runtime6, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles5;
128
+ var import_react_native_skia, import_core3, import_react4, import_react_native12, import_react_native_reanimated, import_jsx_runtime10, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles9;
129
129
  var init_TriggerGauge = __esm({
130
130
  "src/TriggerGauge.tsx"() {
131
131
  "use strict";
132
132
  import_react_native_skia = require("@shopify/react-native-skia");
133
133
  import_core3 = require("@tastic/core");
134
- import_react3 = require("react");
135
- import_react_native8 = require("react-native");
134
+ import_react4 = require("react");
135
+ import_react_native12 = require("react-native");
136
136
  import_react_native_reanimated = require("react-native-reanimated");
137
- import_jsx_runtime6 = require("react/jsx-runtime");
137
+ import_jsx_runtime10 = require("react/jsx-runtime");
138
138
  UNLIT_THICKNESS = 3;
139
139
  LIT_THICKNESS = 7;
140
140
  DASH_GAP_PX = 9;
@@ -144,7 +144,7 @@ var init_TriggerGauge = __esm({
144
144
  MOUNT_DURATION_MS = 450;
145
145
  MOUNT_STAGGER_MS = 40;
146
146
  SELECTION_TRANSITION_DURATION_MS = 260;
147
- styles5 = import_react_native8.StyleSheet.create({
147
+ styles9 = import_react_native12.StyleSheet.create({
148
148
  container: {
149
149
  alignItems: "center",
150
150
  justifyContent: "center",
@@ -157,34 +157,112 @@ var init_TriggerGauge = __esm({
157
157
  // src/index.ts
158
158
  var index_exports = {};
159
159
  __export(index_exports, {
160
+ AchievementRow: () => AchievementRow,
160
161
  BaseSettingsDialog: () => BaseSettingsDialog,
162
+ BaseStatsScreen: () => BaseStatsScreen,
163
+ CornerActionButtons: () => CornerActionButtons,
161
164
  InlineColorPicker: () => InlineColorPicker,
165
+ LABELED_DROPDOWN_POPOVER_WIDTH: () => LABELED_DROPDOWN_POPOVER_WIDTH,
166
+ LOCKED_BADGE_COLOR: () => LOCKED_BADGE_COLOR,
167
+ LabeledDropdown: () => LabeledDropdown,
162
168
  MONO_FONT: () => MONO_FONT,
163
169
  PopoverBody: () => PopoverBody,
164
170
  PressAwayOverlay: () => PressAwayOverlay,
165
171
  ReadyButton: () => ReadyButton,
166
172
  SectionedDropdown: () => SectionedDropdown,
173
+ SharedActionBand: () => SharedActionBand,
174
+ StatRow: () => StatRow,
175
+ StatSection: () => StatSection,
167
176
  TriggerGaugeHost: () => TriggerGaugeHost,
177
+ getLabeledDropdownContentHeight: () => getLabeledDropdownContentHeight,
168
178
  loadSkiaWeb: () => loadSkiaWeb,
169
179
  useAutoAlign: () => useAutoAlign,
170
180
  usePopoverHost: () => usePopoverHost
171
181
  });
172
182
  module.exports = __toCommonJS(index_exports);
173
183
 
174
- // src/BaseSettingsDialog.tsx
184
+ // src/AchievementRow.tsx
175
185
  var import_auto_paper = require("@rific/auto-paper");
186
+ var import_react_native = require("react-native");
187
+ var import_react_native_paper = require("react-native-paper");
188
+ var import_jsx_runtime = require("react/jsx-runtime");
189
+ var LOCKED_BADGE_COLOR = "rgba(128,128,128,0.3)";
190
+ function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker }) {
191
+ const { dark } = (0, import_auto_paper.useAutoPaperTheme)();
192
+ const fg = dark ? "#FFFFFF" : "#000000";
193
+ const fgMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
194
+ const sectionBg = dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)";
195
+ const unlocked = unlockedLabel !== void 0;
196
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.row, { backgroundColor: sectionBg }], children: [
197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.badge, { backgroundColor: badgeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: icon, size: 20, color: unlocked ? "#000000" : fgMuted }) }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.text, children: [
199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: [styles.boldText, { color: fg }], children: title }),
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodySmall", style: { color: fgMuted }, children: description }),
201
+ deviceMarker && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.deviceMarker, children: [
202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "earth", size: 11, color: fgMuted }),
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "labelSmall", style: { color: fgMuted }, children: "Same for every profile" })
204
+ ] }),
205
+ !unlocked && progress !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.ProgressBar, { progress, color: checkColor ?? badgeColor, style: styles.progressBar })
206
+ ] }),
207
+ unlocked ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.status, children: [
208
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "check-circle", size: 20, color: checkColor ?? badgeColor }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "labelSmall", style: { color: fgMuted }, children: unlockedLabel })
210
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "lock-outline", size: 20, color: fgMuted })
211
+ ] });
212
+ }
213
+ var styles = import_react_native.StyleSheet.create({
214
+ badge: {
215
+ alignItems: "center",
216
+ borderRadius: 10,
217
+ height: 36,
218
+ justifyContent: "center",
219
+ width: 36
220
+ },
221
+ boldText: {
222
+ fontWeight: "bold"
223
+ },
224
+ deviceMarker: {
225
+ alignItems: "center",
226
+ flexDirection: "row",
227
+ gap: 4
228
+ },
229
+ progressBar: {
230
+ borderRadius: 4,
231
+ height: 6,
232
+ marginTop: 6
233
+ },
234
+ row: {
235
+ alignItems: "center",
236
+ borderRadius: 12,
237
+ flexDirection: "row",
238
+ gap: 12,
239
+ padding: 12
240
+ },
241
+ status: {
242
+ alignItems: "center",
243
+ gap: 2,
244
+ width: 64
245
+ },
246
+ text: {
247
+ flex: 1,
248
+ gap: 2
249
+ }
250
+ });
251
+
252
+ // src/BaseSettingsDialog.tsx
253
+ var import_auto_paper2 = require("@rific/auto-paper");
176
254
  var import_feedback_press = require("@rific/feedback-press");
177
255
  var import_updater = require("@rific/updater");
178
256
  var import_core = require("@tastic/core");
179
257
  var import_react = require("react");
180
- var import_react_native = require("react-native");
181
- var import_react_native_paper = require("react-native-paper");
182
- var import_jsx_runtime = require("react/jsx-runtime");
258
+ var import_react_native2 = require("react-native");
259
+ var import_react_native_paper2 = require("react-native-paper");
260
+ var import_jsx_runtime2 = require("react/jsx-runtime");
183
261
  function SettingIcon({ source, color, containerColor }) {
184
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source, size: 18, color }) });
262
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source, size: 18, color }) });
185
263
  }
186
264
  function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
187
- const { dark, colors } = (0, import_auto_paper.useAutoPaperTheme)();
265
+ const { dark, colors } = (0, import_auto_paper2.useAutoPaperTheme)();
188
266
  const isTouchPrimary = (0, import_core.useIsTouchPrimaryDevice)();
189
267
  const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press.useHapticSettings)();
190
268
  const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press.useSoundSettings)();
@@ -198,35 +276,35 @@ function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOri
198
276
  onInfo: (title, message) => setInfoMessage({ title, message })
199
277
  });
200
278
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
201
- const showEdgeGuard = import_react_native.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
202
- const showHaptics = import_react_native.Platform.OS !== "web" && !hideHaptics;
279
+ const showEdgeGuard = import_react_native2.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
280
+ const showHaptics = import_react_native2.Platform.OS !== "web" && !hideHaptics;
203
281
  const showSound = !hideSound;
204
282
  const showAppearance = !hideAppearance;
205
- const showUpdateCheck = import_react_native.Platform.OS !== "web" && !hideUpdateCheck;
283
+ const showUpdateCheck = import_react_native2.Platform.OS !== "web" && !hideUpdateCheck;
206
284
  const fg = dark ? "#FFFFFF" : "#000000";
207
285
  const cardBg = dark ? "#111111" : "#F2F2F2";
208
286
  const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
209
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
210
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_auto_paper.Dialog, { visible, onDismiss, style: [styles.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
211
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_auto_paper.Dialog.Title, { children: "Settings" }),
212
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_auto_paper.Dialog.ScrollArea, { style: styles.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.ScrollView, { contentContainerStyle: styles.content, showsVerticalScrollIndicator: false, children: [
213
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleGroup, children: [
214
- showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
215
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
216
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.flexShrink, children: [
217
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
218
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
287
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_auto_paper2.Dialog, { visible, onDismiss, style: [styles2.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
289
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.Dialog.Title, { children: "Settings" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.Dialog.ScrollArea, { style: styles2.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.ScrollView, { contentContainerStyle: styles2.content, showsVerticalScrollIndicator: false, children: [
291
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.toggleGroup, children: [
292
+ showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles2.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.toggleContent, children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
294
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.flexShrink, children: [
295
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
219
297
  ] })
220
298
  ] }) }),
221
- showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
222
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
223
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.flexShrink, children: [
224
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
225
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
299
+ showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles2.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.toggleContent, children: [
300
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
301
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.flexShrink, children: [
302
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
303
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
226
304
  ] })
227
305
  ] }) }),
228
- (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleRow, children: [
229
- showSound && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
306
+ (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.toggleRow, children: [
307
+ showSound && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
230
308
  import_feedback_press.TouchableRipple,
231
309
  {
232
310
  soundDisabled: true,
@@ -235,15 +313,15 @@ function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOri
235
313
  setSoundSettings({ enabled });
236
314
  if (enabled) sound.selection?.();
237
315
  },
238
- style: styles.toggleButtonInRow,
316
+ style: styles2.toggleButtonInRow,
239
317
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
240
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
241
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
242
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
318
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.toggleContent, children: [
319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
243
321
  ] })
244
322
  }
245
323
  ),
246
- showHaptics && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
324
+ showHaptics && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
247
325
  import_feedback_press.TouchableRipple,
248
326
  {
249
327
  hapticDisabled: true,
@@ -252,40 +330,40 @@ function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOri
252
330
  setHapticSettings({ vibrate });
253
331
  if (vibrate) forceHapticFeedback();
254
332
  },
255
- style: styles.toggleButtonInRow,
333
+ style: styles2.toggleButtonInRow,
256
334
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
257
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
258
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
259
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
335
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.toggleContent, children: [
336
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
337
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
260
338
  ] })
261
339
  }
262
340
  )
263
341
  ] })
264
342
  ] }),
265
- showAppearance && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.section, children: [
266
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "labelMedium", style: [styles.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
267
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_auto_paper.AutoAppearancePicker, { showLabels: false })
343
+ showAppearance && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.section, children: [
344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
345
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.AutoAppearancePicker, { showLabels: false })
268
346
  ] }),
269
347
  children,
270
- showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.section, children: [
271
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_paper.Text, { variant: "labelSmall", style: [styles.sectionLabel, { color: colors.onSurfaceVariant }], children: [
348
+ showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.section, children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_paper2.Text, { variant: "labelSmall", style: [styles2.sectionLabel, { color: colors.onSurfaceVariant }], children: [
272
350
  "VERSION ",
273
351
  version,
274
352
  updateReady ? " \xB7 UPDATE READY" : ""
275
353
  ] }),
276
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
354
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
277
355
  ] })
278
356
  ] }) })
279
357
  ] }),
280
- infoMessage && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: styles.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
281
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "information-outline", size: 64, color: colors.secondary }),
282
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "headlineLarge", style: [styles.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
283
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: [styles.overlayBody, { color: fg }], children: infoMessage.message }),
284
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.Button, { mode: "contained", onPress: () => setInfoMessage(null), style: styles.overlayButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: "OK" })
358
+ infoMessage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
359
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "information-outline", size: 64, color: colors.secondary }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineLarge", style: [styles2.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
361
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: [styles2.overlayBody, { color: fg }], children: infoMessage.message }),
362
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "contained", onPress: () => setInfoMessage(null), style: styles2.overlayButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: "OK" })
285
363
  ] }) }) })
286
364
  ] });
287
365
  }
288
- var styles = import_react_native.StyleSheet.create({
366
+ var styles2 = import_react_native2.StyleSheet.create({
289
367
  content: {
290
368
  gap: 24,
291
369
  paddingVertical: 20
@@ -373,42 +451,161 @@ var styles = import_react_native.StyleSheet.create({
373
451
  }
374
452
  });
375
453
 
454
+ // src/BaseStatsScreen.tsx
455
+ var import_auto_paper3 = require("@rific/auto-paper");
456
+ var import_feedback_press2 = require("@rific/feedback-press");
457
+ var import_react2 = require("react");
458
+ var import_react_native3 = require("react-native");
459
+ var import_react_native_paper3 = require("react-native-paper");
460
+ var import_jsx_runtime3 = require("react/jsx-runtime");
461
+ function BaseStatsScreen({ rotation = 0, title = "Achievements", onBack, insets, onReset, resetLabel = "Reset All Stats", resetConfirmTitle = "Reset Everything?", resetConfirmBody = "This permanently erases all stats and achievements. This cannot be undone.", children }) {
462
+ const { dark, colors } = (0, import_auto_paper3.useAutoPaperTheme)();
463
+ const [confirmResetVisible, setConfirmResetVisible] = (0, import_react2.useState)(false);
464
+ const showReset = !!onReset;
465
+ const fg = dark ? "#FFFFFF" : "#000000";
466
+ const bg = dark ? "#000000" : "#FFFFFF";
467
+ const cardBg = dark ? "#111111" : "#F2F2F2";
468
+ const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
469
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.container, { backgroundColor: bg }], children: [
470
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
471
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: onBack }),
472
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "displaySmall", style: [styles3.title, { color: fg }], children: title })
473
+ ] }),
474
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
475
+ children,
476
+ showReset && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: () => setConfirmResetVisible(true), textColor: colors.secondary, style: styles3.resetButton, children: resetLabel })
477
+ ] }),
478
+ confirmResetVisible && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.overlay, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }], children: [
479
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "alert-outline", size: 64, color: colors.secondary }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "headlineLarge", style: [styles3.overlayTitle, { color: colors.secondary }], children: resetConfirmTitle }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.overlayBody, { color: fg }], children: resetConfirmBody }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "contained", onPress: () => setConfirmResetVisible(false), style: styles3.overlayButton, children: "Cancel" }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
484
+ import_feedback_press2.Button,
485
+ {
486
+ mode: "contained",
487
+ onPress: () => {
488
+ onReset?.();
489
+ setConfirmResetVisible(false);
490
+ },
491
+ style: styles3.overlayButton,
492
+ buttonColor: colors.secondary,
493
+ textColor: colors.onSecondary,
494
+ children: "Reset"
495
+ }
496
+ )
497
+ ] }) }) })
498
+ ] });
499
+ }
500
+ var styles3 = import_react_native3.StyleSheet.create({
501
+ container: {
502
+ flex: 1
503
+ },
504
+ content: {
505
+ gap: 12,
506
+ paddingHorizontal: 20
507
+ },
508
+ header: {
509
+ alignItems: "center",
510
+ flexDirection: "row",
511
+ gap: 4,
512
+ paddingBottom: 8
513
+ },
514
+ // Same overlay shape BaseSettingsDialog's own info overlay uses (backdrop + centered card),
515
+ // rotated in place the same way rather than via Portal-root transform for the same reason — see
516
+ // that component's own identical styles for the full rationale.
517
+ overlay: {
518
+ alignItems: "center",
519
+ backgroundColor: "rgba(0,0,0,0.72)",
520
+ bottom: 0,
521
+ justifyContent: "center",
522
+ left: 0,
523
+ position: "absolute",
524
+ right: 0,
525
+ top: 0
526
+ },
527
+ overlayBody: { textAlign: "center" },
528
+ overlayButton: { width: 160 },
529
+ overlayCard: {
530
+ alignItems: "center",
531
+ borderRadius: 20,
532
+ borderWidth: 1,
533
+ gap: 16,
534
+ maxWidth: 360,
535
+ padding: 32
536
+ },
537
+ overlayTitle: { fontWeight: "bold", marginBottom: -8 },
538
+ resetButton: {
539
+ marginTop: 16
540
+ },
541
+ // Bounds the ScrollView to the space `container`'s flex:1 actually gives it — without this, a
542
+ // ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
543
+ // can render at its full unclipped content height instead of the screen's), which leaves the
544
+ // header's back button touch target unreliable underneath it.
545
+ scrollView: {
546
+ flex: 1
547
+ },
548
+ title: {
549
+ flexShrink: 1,
550
+ fontWeight: "bold"
551
+ }
552
+ });
553
+
554
+ // src/CornerActionButtons.tsx
555
+ var import_feedback_press3 = require("@rific/feedback-press");
556
+ var import_react_native4 = require("react-native");
557
+ var import_jsx_runtime4 = require("react/jsx-runtime");
558
+ function CornerActionButtons({ onBack, onSettings, fg, insets }) {
559
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
560
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, style: [styles4.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: "Back" }),
561
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles4.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
562
+ ] });
563
+ }
564
+ var styles4 = import_react_native4.StyleSheet.create({
565
+ back: {
566
+ position: "absolute"
567
+ },
568
+ topRight: {
569
+ position: "absolute"
570
+ }
571
+ });
572
+
376
573
  // src/fonts.ts
377
- var import_react_native2 = require("react-native");
378
- var MONO_FONT = import_react_native2.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
574
+ var import_react_native5 = require("react-native");
575
+ var MONO_FONT = import_react_native5.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
379
576
 
380
577
  // src/InlineColorPicker.tsx
381
- var import_auto_paper2 = require("@rific/auto-paper");
382
- var import_feedback_press2 = require("@rific/feedback-press");
578
+ var import_auto_paper4 = require("@rific/auto-paper");
579
+ var import_feedback_press4 = require("@rific/feedback-press");
383
580
  var import_core2 = require("@tastic/core");
384
- var import_react_native5 = require("react-native");
385
- var import_react_native_paper2 = require("react-native-paper");
581
+ var import_react_native8 = require("react-native");
582
+ var import_react_native_paper4 = require("react-native-paper");
386
583
 
387
584
  // src/PopoverBody.tsx
388
- var import_react_native3 = require("react-native");
389
- var import_jsx_runtime2 = require("react/jsx-runtime");
585
+ var import_react_native6 = require("react-native");
586
+ var import_jsx_runtime5 = require("react/jsx-runtime");
390
587
  var CARET_SIZE = 7;
391
588
  var CARET_RING_OFFSET = 2;
392
589
  var CARET_DIP = 1;
393
590
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
394
591
  if (!visible) return null;
395
592
  const above = verticalAlign === "above";
396
- const alignStyle = align === "left" ? styles2.contentLeft : align === "right" ? styles2.contentRight : styles2.contentCenter;
397
- const verticalStyle = above ? styles2.contentAbove : styles2.contentBelow;
593
+ const alignStyle = align === "left" ? styles5.contentLeft : align === "right" ? styles5.contentRight : styles5.contentCenter;
594
+ const verticalStyle = above ? styles5.contentAbove : styles5.contentBelow;
398
595
  const ringSize = CARET_SIZE + caretBorderWidth;
399
596
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
400
597
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
401
598
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
402
599
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
403
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [styles2.content, alignStyle, verticalStyle], children: [
600
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native6.View, { style: [styles5.content, alignStyle, verticalStyle], children: [
404
601
  children,
405
- caretColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
406
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [styles2.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
407
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [styles2.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
602
+ caretColor && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
603
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles5.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
604
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles5.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
408
605
  ] })
409
606
  ] });
410
607
  }
411
- var styles2 = import_react_native3.StyleSheet.create({
608
+ var styles5 = import_react_native6.StyleSheet.create({
412
609
  caret: {
413
610
  borderLeftColor: "transparent",
414
611
  borderRightColor: "transparent",
@@ -451,22 +648,22 @@ var styles2 = import_react_native3.StyleSheet.create({
451
648
  });
452
649
 
453
650
  // src/useAutoAlign.ts
454
- var import_react2 = require("react");
455
- var import_react_native4 = require("react-native");
651
+ var import_react3 = require("react");
652
+ var import_react_native7 = require("react-native");
456
653
  var EDGE_MARGIN = 12;
457
654
  function useAutoAlign(open, contentWidth, contentHeight) {
458
- const triggerRef = (0, import_react2.useRef)(null);
459
- const { width: windowWidth, height: windowHeight } = (0, import_react_native4.useWindowDimensions)();
460
- const [align, setAlign] = (0, import_react2.useState)("center");
461
- const [verticalAlign, setVerticalAlign] = (0, import_react2.useState)("below");
462
- const [maxHeight, setMaxHeight] = (0, import_react2.useState)(contentHeight);
463
- const [measured, setMeasured] = (0, import_react2.useState)(false);
464
- const [prevOpen, setPrevOpen] = (0, import_react2.useState)(open);
655
+ const triggerRef = (0, import_react3.useRef)(null);
656
+ const { width: windowWidth, height: windowHeight } = (0, import_react_native7.useWindowDimensions)();
657
+ const [align, setAlign] = (0, import_react3.useState)("center");
658
+ const [verticalAlign, setVerticalAlign] = (0, import_react3.useState)("below");
659
+ const [maxHeight, setMaxHeight] = (0, import_react3.useState)(contentHeight);
660
+ const [measured, setMeasured] = (0, import_react3.useState)(false);
661
+ const [prevOpen, setPrevOpen] = (0, import_react3.useState)(open);
465
662
  if (open !== prevOpen) {
466
663
  setPrevOpen(open);
467
664
  if (open) setMeasured(false);
468
665
  }
469
- const measure = (0, import_react2.useCallback)(() => {
666
+ const measure = (0, import_react3.useCallback)(() => {
470
667
  triggerRef.current?.measureInWindow((x, y, triggerWidth, triggerHeight) => {
471
668
  const centerX = x + triggerWidth / 2;
472
669
  const overflowsRight = centerX + contentWidth / 2 > windowWidth - EDGE_MARGIN;
@@ -482,14 +679,14 @@ function useAutoAlign(open, contentWidth, contentHeight) {
482
679
  setMeasured(true);
483
680
  });
484
681
  }, [contentWidth, contentHeight, windowWidth, windowHeight]);
485
- (0, import_react2.useEffect)(() => {
682
+ (0, import_react3.useEffect)(() => {
486
683
  if (open) measure();
487
684
  }, [open, measure]);
488
685
  return { align, maxHeight, measured, triggerRef, verticalAlign };
489
686
  }
490
687
 
491
688
  // src/InlineColorPicker.tsx
492
- var import_jsx_runtime3 = require("react/jsx-runtime");
689
+ var import_jsx_runtime6 = require("react/jsx-runtime");
493
690
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
494
691
  var DEFAULT_SIZE = 48;
495
692
  var SWATCH_SIZE = 28;
@@ -502,9 +699,9 @@ var SCREEN_MARGIN = 40;
502
699
  function widthForColumns(columns) {
503
700
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
504
701
  }
505
- function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_paper2.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
702
+ function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_paper4.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
506
703
  const menuBg = dark ? "#000000" : "#FFFFFF";
507
- const { width: windowWidth } = (0, import_react_native5.useWindowDimensions)();
704
+ const { width: windowWidth } = (0, import_react_native8.useWindowDimensions)();
508
705
  const autoColumns = (0, import_core2.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
509
706
  const resolvedColumns = columns ?? autoColumns;
510
707
  const swatchesWidth = widthForColumns(resolvedColumns);
@@ -513,16 +710,16 @@ function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_p
513
710
  const open = host.openId === id;
514
711
  const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight);
515
712
  const align = alignOverride ?? autoAlign;
516
- const contrastColor = (0, import_auto_paper2.getContrastColor)(value);
713
+ const contrastColor = (0, import_auto_paper4.getContrastColor)(value);
517
714
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
518
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: [styles3.anchor, open && styles3.anchorOpen], children: [
519
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles3.trigger, { backgroundColor: value, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { ref: triggerRef, collapsable: false, style: styles3.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles3.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
520
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: value, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.ScrollView, { style: [styles3.swatches, { backgroundColor: menuBg, borderColor: value, width: swatchesWidth, maxHeight }], contentContainerStyle: styles3.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
715
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native8.View, { style: [styles6.anchor, open && styles6.anchorOpen], children: [
716
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles6.trigger, { backgroundColor: value, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.View, { ref: triggerRef, collapsable: false, style: styles6.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper4.Text, { style: [styles6.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper4.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
717
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: value, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.ScrollView, { style: [styles6.swatches, { backgroundColor: menuBg, borderColor: value, width: swatchesWidth, maxHeight }], contentContainerStyle: styles6.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
521
718
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
522
719
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
523
720
  const swappable = taken && allowSwapTaken;
524
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
525
- import_feedback_press2.TouchableRipple,
721
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
722
+ import_feedback_press4.TouchableRipple,
526
723
  {
527
724
  disabled: taken && !swappable,
528
725
  onPress: () => {
@@ -530,15 +727,15 @@ function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_p
530
727
  if (autoDismiss) host.close();
531
728
  },
532
729
  borderless: true,
533
- style: [styles3.swatch, { backgroundColor: swatch.value }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken],
534
- children: selected ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatch.value) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatch.value) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatch.value) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, {})
730
+ style: [styles6.swatch, { backgroundColor: swatch.value }, selected && styles6.swatchSelected, taken && !swappable && styles6.swatchTaken],
731
+ children: selected ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper4.Icon, { source: "check", size: 16, color: (0, import_auto_paper4.getContrastColor)(swatch.value) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper4.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper4.getContrastColor)(swatch.value) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper4.Icon, { source: "close", size: 16, color: (0, import_auto_paper4.getContrastColor)(swatch.value) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.View, {})
535
732
  },
536
733
  swatch.value
537
734
  );
538
735
  }) }) })
539
736
  ] });
540
737
  }
541
- var styles3 = import_react_native5.StyleSheet.create({
738
+ var styles6 = import_react_native8.StyleSheet.create({
542
739
  anchor: {
543
740
  position: "relative"
544
741
  },
@@ -609,31 +806,126 @@ var styles3 = import_react_native5.StyleSheet.create({
609
806
  }
610
807
  });
611
808
 
809
+ // src/LabeledDropdown.tsx
810
+ var import_auto_paper5 = require("@rific/auto-paper");
811
+ var import_feedback_press5 = require("@rific/feedback-press");
812
+ var import_react_native9 = require("react-native");
813
+ var import_react_native_paper5 = require("react-native-paper");
814
+ var import_jsx_runtime7 = require("react/jsx-runtime");
815
+ var TRIGGER_HEIGHT = 28;
816
+ var POPOVER_WIDTH = 180;
817
+ var ROW_HEIGHT = 36;
818
+ var LIST_PADDING = 8;
819
+ var LABELED_DROPDOWN_POPOVER_WIDTH = POPOVER_WIDTH;
820
+ function getLabeledDropdownContentHeight(optionCount) {
821
+ return LIST_PADDING * 2 + optionCount * ROW_HEIGHT;
822
+ }
823
+ function LabeledDropdown({ id, host, options, value, onChange, color, dark, align: forcedAlign, alignOverride }) {
824
+ const menuBg = dark ? "#000000" : "#FFFFFF";
825
+ const fg = dark ? "#FFFFFF" : "#000000";
826
+ const open = host.openId === id;
827
+ const selected = options.find((o) => o.value === value) ?? null;
828
+ const contentHeight = LIST_PADDING * 2 + options.length * ROW_HEIGHT;
829
+ const auto = useAutoAlign(open, POPOVER_WIDTH, contentHeight);
830
+ const placement = alignOverride ?? auto;
831
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
832
+ const align = forcedAlign ?? placement.align;
833
+ const handleSelect = (option) => {
834
+ onChange(option.value);
835
+ host.close();
836
+ };
837
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { style: [styles7.anchor, open && styles7.anchorOpen], children: [
838
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press5.TouchableRipple, { onPress: () => host.toggle(id), style: styles7.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { ref: triggerRef, collapsable: false, style: styles7.triggerInner, children: [
839
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper5.Text, { style: [styles7.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
840
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper5.Icon, { source: "menu-down", size: 16, color })
841
+ ] }) }),
842
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native9.ScrollView, { style: [styles7.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles7.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
843
+ const isSelected = option.value === value;
844
+ const onColor = (0, import_auto_paper5.getContrastColor)(color);
845
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press5.TouchableRipple, { onPress: () => handleSelect(option), style: [styles7.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native9.View, { style: styles7.rowInner, children: [
846
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
847
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper5.Text, { style: [styles7.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
848
+ ] }) }, option.value);
849
+ }) }) })
850
+ ] });
851
+ }
852
+ var styles7 = import_react_native9.StyleSheet.create({
853
+ anchor: {
854
+ position: "relative"
855
+ },
856
+ // React Native Web gives every position:'relative' view its own stacking context, so the
857
+ // elevation has to live on the anchor itself — see PopoverBody's own stacking-context notes.
858
+ anchorOpen: {
859
+ zIndex: 100
860
+ },
861
+ menu: {
862
+ borderRadius: 12,
863
+ borderWidth: 2,
864
+ elevation: 8,
865
+ shadowColor: "#000000",
866
+ shadowOffset: { height: 2, width: 0 },
867
+ shadowOpacity: 0.3,
868
+ shadowRadius: 8
869
+ },
870
+ menuContent: {
871
+ padding: LIST_PADDING
872
+ },
873
+ row: {
874
+ borderRadius: 8,
875
+ height: ROW_HEIGHT
876
+ },
877
+ rowInner: {
878
+ alignItems: "center",
879
+ flexDirection: "row",
880
+ flex: 1,
881
+ gap: 8,
882
+ paddingHorizontal: 12
883
+ },
884
+ rowLabel: {
885
+ flex: 1,
886
+ fontSize: 14
887
+ },
888
+ trigger: {
889
+ alignSelf: "flex-start"
890
+ },
891
+ triggerInner: {
892
+ alignItems: "center",
893
+ flexDirection: "row",
894
+ gap: 2,
895
+ height: TRIGGER_HEIGHT
896
+ },
897
+ triggerLabel: {
898
+ fontSize: 13,
899
+ fontWeight: "700",
900
+ letterSpacing: 1
901
+ }
902
+ });
903
+
612
904
  // src/loadSkiaWeb.ts
613
905
  function loadSkiaWeb() {
614
906
  return Promise.resolve();
615
907
  }
616
908
 
617
909
  // src/PressAwayOverlay.tsx
618
- var import_react_native6 = require("react-native");
619
- var import_jsx_runtime4 = require("react/jsx-runtime");
910
+ var import_react_native10 = require("react-native");
911
+ var import_jsx_runtime8 = require("react/jsx-runtime");
620
912
  function PressAwayOverlay({ active, onPress, style }) {
621
913
  if (!active) return null;
622
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native6.StyleSheet.absoluteFill, style] });
914
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native10.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native10.StyleSheet.absoluteFill, style] });
623
915
  }
624
916
 
625
917
  // src/ReadyButton.tsx
626
- var import_feedback_press3 = require("@rific/feedback-press");
627
- var import_react_native7 = require("react-native");
628
- var import_react_native_paper3 = require("react-native-paper");
629
- var import_jsx_runtime5 = require("react/jsx-runtime");
918
+ var import_feedback_press6 = require("@rific/feedback-press");
919
+ var import_react_native11 = require("react-native");
920
+ var import_react_native_paper6 = require("react-native-paper");
921
+ var import_jsx_runtime9 = require("react/jsx-runtime");
630
922
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
631
- const readyButtonStyle = [styles4.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
923
+ const readyButtonStyle = [styles8.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
632
924
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
633
925
  const labelColorStyle = { color: ready ? "#000000" : color };
634
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press3.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
926
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press6.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper6.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
635
927
  }
636
- var styles4 = import_react_native7.StyleSheet.create({
928
+ var styles8 = import_react_native11.StyleSheet.create({
637
929
  readyButton: {
638
930
  alignItems: "center",
639
931
  borderRadius: 12,
@@ -646,21 +938,21 @@ var styles4 = import_react_native7.StyleSheet.create({
646
938
  });
647
939
 
648
940
  // src/SectionedDropdown.tsx
649
- var import_auto_paper3 = require("@rific/auto-paper");
650
- var import_feedback_press4 = require("@rific/feedback-press");
651
- var import_react_native9 = require("react-native");
652
- var import_react_native_paper4 = require("react-native-paper");
941
+ var import_auto_paper6 = require("@rific/auto-paper");
942
+ var import_feedback_press7 = require("@rific/feedback-press");
943
+ var import_react_native13 = require("react-native");
944
+ var import_react_native_paper7 = require("react-native-paper");
653
945
 
654
946
  // src/TriggerGaugeHost.tsx
655
- var import_react4 = require("react");
656
- var import_jsx_runtime7 = require("react/jsx-runtime");
657
- var LazyTriggerGauge = (0, import_react4.lazy)(() => loadSkiaWeb().then(() => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((m) => ({ default: m.TriggerGauge }))));
947
+ var import_react5 = require("react");
948
+ var import_jsx_runtime11 = require("react/jsx-runtime");
949
+ var LazyTriggerGauge = (0, import_react5.lazy)(() => loadSkiaWeb().then(() => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((m) => ({ default: m.TriggerGauge }))));
658
950
  function TriggerGaugeHost(props) {
659
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react4.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LazyTriggerGauge, { ...props }) });
951
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react5.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(LazyTriggerGauge, { ...props }) });
660
952
  }
661
953
 
662
954
  // src/SectionedDropdown.tsx
663
- var import_jsx_runtime8 = require("react/jsx-runtime");
955
+ var import_jsx_runtime12 = require("react/jsx-runtime");
664
956
  var MENU_BORDER_WIDTH = 1;
665
957
  var TRIGGER_SIZE = 44;
666
958
  var MENU_MIN_WIDTH = 200;
@@ -673,8 +965,8 @@ var MENU_DIVIDER_HEIGHT = 9;
673
965
  var MENU_VERTICAL_PADDING = 12;
674
966
  function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
675
967
  const menuBg = dark ? "#000000" : "#FFFFFF";
676
- const menuBorderColor = (0, import_auto_paper3.getBlendedColor)(mutedColor, menuBg, 0.5);
677
- const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper3.getColorRoles)(accentColor, menuBg).onColor;
968
+ const menuBorderColor = (0, import_auto_paper6.getBlendedColor)(mutedColor, menuBg, 0.5);
969
+ const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper6.getColorRoles)(accentColor, menuBg).onColor;
678
970
  const fg = dark ? "#FFFFFF" : "#000000";
679
971
  const open = host.openId === id;
680
972
  const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
@@ -706,17 +998,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
706
998
  }
707
999
  gaugeOffset += section.options.length;
708
1000
  }
709
- const anchorStyle = [styles6.anchor, open && styles6.anchorOpen];
1001
+ const anchorStyle = [styles10.anchor, open && styles10.anchorOpen];
710
1002
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
711
1003
  const dividerStyle = { backgroundColor: menuBorderColor };
712
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: anchorStyle, children: [
713
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { ref: triggerRef, collapsable: false, style: styles6.triggerBox, children: [
714
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
715
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press4.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
1004
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native13.View, { style: anchorStyle, children: [
1005
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native13.View, { ref: triggerRef, collapsable: false, style: styles10.triggerBox, children: [
1006
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
1007
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press7.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
716
1008
  ] }),
717
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.ScrollView, { style: [styles6.menu, menuStyle], contentContainerStyle: styles6.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { children: [
718
- sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.View, { style: [styles6.divider, dividerStyle] }),
719
- section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1009
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native13.ScrollView, { style: [styles10.menu, menuStyle], contentContainerStyle: styles10.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native13.View, { children: [
1010
+ sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native13.View, { style: [styles10.divider, dividerStyle] }),
1011
+ section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
720
1012
  SingleSection,
721
1013
  {
722
1014
  section,
@@ -729,7 +1021,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
729
1021
  if (autoDismiss) host.close();
730
1022
  }
731
1023
  }
732
- ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
1024
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
733
1025
  ] }, section.id)) }) })
734
1026
  ] });
735
1027
  }
@@ -741,17 +1033,17 @@ function SingleSection({
741
1033
  labelFontFamily,
742
1034
  onSelect
743
1035
  }) {
744
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: section.options.map((option) => {
1036
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_jsx_runtime12.Fragment, { children: section.options.map((option) => {
745
1037
  const selected = option.value === section.value;
746
1038
  const rowColor = selected ? onAccentColor : mutedColor;
747
- const itemStyle = [styles6.item, selected && { backgroundColor: accentColor }];
1039
+ const itemStyle = [styles10.item, selected && { backgroundColor: accentColor }];
748
1040
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
749
1041
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
750
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles6.itemRow, children: [
751
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
752
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles6.itemLabelColumn, children: [
753
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
754
- option.description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
1042
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native13.View, { style: styles10.itemRow, children: [
1043
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper7.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
1044
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native13.View, { style: styles10.itemLabelColumn, children: [
1045
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper7.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
1046
+ option.description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper7.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
755
1047
  ] })
756
1048
  ] }) }, option.value);
757
1049
  }) });
@@ -765,23 +1057,23 @@ function MultiSection({
765
1057
  allClearLabels
766
1058
  }) {
767
1059
  const allSelected = section.value.length === section.options.length;
768
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1060
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
769
1061
  section.options.map((option, i) => {
770
1062
  const selected = section.value.includes(option.value);
771
1063
  const rowColor = selected ? onAccentColor : mutedColor;
772
1064
  const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
773
1065
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
774
- const itemStyle = [styles6.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
1066
+ const itemStyle = [styles10.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
775
1067
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
776
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native9.View, { style: styles6.itemRow, children: [
777
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
778
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "labelLarge", style: [styles6.itemLabelFlex, labelStyle], children: option.label })
1068
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native13.View, { style: styles10.itemRow, children: [
1069
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper7.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper7.Text, { variant: "labelLarge", style: [styles10.itemLabelFlex, labelStyle], children: option.label })
779
1071
  ] }) }, option.value);
780
1072
  }),
781
- section.allClear && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles6.item, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "labelLarge", style: [styles6.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
1073
+ section.allClear && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles10.item, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper7.Text, { variant: "labelLarge", style: [styles10.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
782
1074
  ] });
783
1075
  }
784
- var styles6 = import_react_native9.StyleSheet.create({
1076
+ var styles10 = import_react_native13.StyleSheet.create({
785
1077
  allClearLabel: {
786
1078
  fontWeight: "bold",
787
1079
  textAlign: "center"
@@ -798,7 +1090,7 @@ var styles6 = import_react_native9.StyleSheet.create({
798
1090
  zIndex: 100
799
1091
  },
800
1092
  divider: {
801
- height: import_react_native9.StyleSheet.hairlineWidth,
1093
+ height: import_react_native13.StyleSheet.hairlineWidth,
802
1094
  marginVertical: 4
803
1095
  },
804
1096
  item: {
@@ -845,26 +1137,116 @@ var styles6 = import_react_native9.StyleSheet.create({
845
1137
  }
846
1138
  });
847
1139
 
1140
+ // src/SharedActionBand.tsx
1141
+ var import_feedback_press8 = require("@rific/feedback-press");
1142
+ var import_react_native14 = require("react-native");
1143
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1144
+ function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1145
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native14.View, { style: [styles11.column, popoverOpen && styles11.columnOpen], children: [
1146
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native14.View, { style: styles11.actionsRow, children: [
1147
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_feedback_press8.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1148
+ onRandomize && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_feedback_press8.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1149
+ onReset && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_feedback_press8.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1150
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_feedback_press8.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1151
+ ] }),
1152
+ children
1153
+ ] });
1154
+ }
1155
+ var styles11 = import_react_native14.StyleSheet.create({
1156
+ actionsRow: {
1157
+ alignItems: "center",
1158
+ flexDirection: "row",
1159
+ gap: 16
1160
+ },
1161
+ column: {
1162
+ alignItems: "center",
1163
+ gap: 8
1164
+ },
1165
+ columnOpen: {
1166
+ zIndex: 100
1167
+ }
1168
+ });
1169
+
1170
+ // src/StatRow.tsx
1171
+ var import_auto_paper7 = require("@rific/auto-paper");
1172
+ var import_react_native15 = require("react-native");
1173
+ var import_react_native_paper8 = require("react-native-paper");
1174
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1175
+ function StatRow({ label, value }) {
1176
+ const { dark } = (0, import_auto_paper7.useAutoPaperTheme)();
1177
+ const fg = dark ? "#FFFFFF" : "#000000";
1178
+ const fgMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
1179
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native15.View, { style: styles12.row, children: [
1180
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1181
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native_paper8.Text, { variant: "bodyMedium", style: [styles12.value, { color: fg }], children: value })
1182
+ ] });
1183
+ }
1184
+ var styles12 = import_react_native15.StyleSheet.create({
1185
+ row: {
1186
+ alignItems: "center",
1187
+ flexDirection: "row",
1188
+ justifyContent: "space-between"
1189
+ },
1190
+ value: {
1191
+ fontWeight: "bold"
1192
+ }
1193
+ });
1194
+
1195
+ // src/StatSection.tsx
1196
+ var import_auto_paper8 = require("@rific/auto-paper");
1197
+ var import_react_native16 = require("react-native");
1198
+ var import_react_native_paper9 = require("react-native-paper");
1199
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1200
+ function StatSection({ label, children }) {
1201
+ const { dark } = (0, import_auto_paper8.useAutoPaperTheme)();
1202
+ const fgMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
1203
+ const sectionBg = dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)";
1204
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native16.View, { style: [styles13.section, { backgroundColor: sectionBg }], children: [
1205
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_paper9.Text, { variant: "labelMedium", style: [styles13.label, { color: fgMuted }], children: label }),
1206
+ children
1207
+ ] });
1208
+ }
1209
+ var styles13 = import_react_native16.StyleSheet.create({
1210
+ label: {
1211
+ letterSpacing: 2
1212
+ },
1213
+ section: {
1214
+ borderRadius: 12,
1215
+ gap: 8,
1216
+ padding: 16
1217
+ }
1218
+ });
1219
+
848
1220
  // src/usePopoverHost.ts
849
- var import_react5 = require("react");
1221
+ var import_react6 = require("react");
850
1222
  function usePopoverHost() {
851
- const [openId, setOpenId] = (0, import_react5.useState)(null);
852
- const toggle = (0, import_react5.useCallback)((id) => {
1223
+ const [openId, setOpenId] = (0, import_react6.useState)(null);
1224
+ const toggle = (0, import_react6.useCallback)((id) => {
853
1225
  setOpenId((prev) => prev === id ? null : id);
854
1226
  }, []);
855
- const close = (0, import_react5.useCallback)(() => setOpenId(null), []);
1227
+ const close = (0, import_react6.useCallback)(() => setOpenId(null), []);
856
1228
  return { openId, toggle, close };
857
1229
  }
858
1230
  // Annotate the CommonJS export names for ESM import in node:
859
1231
  0 && (module.exports = {
1232
+ AchievementRow,
860
1233
  BaseSettingsDialog,
1234
+ BaseStatsScreen,
1235
+ CornerActionButtons,
861
1236
  InlineColorPicker,
1237
+ LABELED_DROPDOWN_POPOVER_WIDTH,
1238
+ LOCKED_BADGE_COLOR,
1239
+ LabeledDropdown,
862
1240
  MONO_FONT,
863
1241
  PopoverBody,
864
1242
  PressAwayOverlay,
865
1243
  ReadyButton,
866
1244
  SectionedDropdown,
1245
+ SharedActionBand,
1246
+ StatRow,
1247
+ StatSection,
867
1248
  TriggerGaugeHost,
1249
+ getLabeledDropdownContentHeight,
868
1250
  loadSkiaWeb,
869
1251
  useAutoAlign,
870
1252
  usePopoverHost