@tastic/hud 0.8.3 → 0.8.5

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.d.mts CHANGED
@@ -64,11 +64,11 @@ declare function BaseStatsScreen({ rotation: rotationOverride, title, titleVaria
64
64
  interface ConfirmDialogProps {
65
65
  visible: boolean;
66
66
  title: string;
67
- message: string;
67
+ message: ReactNode;
68
68
  confirmLabel: string;
69
- cancelLabel: string;
69
+ cancelLabel?: string;
70
70
  onConfirm: () => void;
71
- onCancel: () => void;
71
+ onCancel?: () => void;
72
72
  rotation?: ViewRotation;
73
73
  icon?: string;
74
74
  destructive?: boolean;
@@ -337,4 +337,12 @@ interface TriggerGaugeProps {
337
337
 
338
338
  declare function TriggerGaugeHost(props: TriggerGaugeProps): react.JSX.Element;
339
339
 
340
- export { AchievementRow, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, type ConfirmDialogProps, ContentGutter, ControlSchemePicker, type ControlSchemePickerProps, CornerActionButtons, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LOCKED_BADGE_COLOR, LabeledDropdown, type LabeledDropdownOption, MONO_FONT, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, type OverlayCardColors, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, StatRow, StatSection, TriggerGaugeHost, type TriggerGaugeProps, getLabeledDropdownContentHeight, getOverlayCardColors, loadSkiaWeb, overlayStyles, useAutoAlign, usePopoverHost };
340
+ interface UpdateDialogProps {
341
+ rotation?: ViewRotation;
342
+ autoCheck?: boolean;
343
+ autoPrompt?: boolean;
344
+ onError?: (message: string) => void;
345
+ }
346
+ declare function UpdateDialog({ rotation, autoCheck, autoPrompt, onError }: UpdateDialogProps): react.JSX.Element;
347
+
348
+ export { AchievementRow, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, type ConfirmDialogProps, ContentGutter, ControlSchemePicker, type ControlSchemePickerProps, CornerActionButtons, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LOCKED_BADGE_COLOR, LabeledDropdown, type LabeledDropdownOption, MONO_FONT, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, type OverlayCardColors, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, StatRow, StatSection, TriggerGaugeHost, type TriggerGaugeProps, UpdateDialog, type UpdateDialogProps, getLabeledDropdownContentHeight, getOverlayCardColors, loadSkiaWeb, overlayStyles, useAutoAlign, usePopoverHost };
package/dist/index.d.ts CHANGED
@@ -64,11 +64,11 @@ declare function BaseStatsScreen({ rotation: rotationOverride, title, titleVaria
64
64
  interface ConfirmDialogProps {
65
65
  visible: boolean;
66
66
  title: string;
67
- message: string;
67
+ message: ReactNode;
68
68
  confirmLabel: string;
69
- cancelLabel: string;
69
+ cancelLabel?: string;
70
70
  onConfirm: () => void;
71
- onCancel: () => void;
71
+ onCancel?: () => void;
72
72
  rotation?: ViewRotation;
73
73
  icon?: string;
74
74
  destructive?: boolean;
@@ -337,4 +337,12 @@ interface TriggerGaugeProps {
337
337
 
338
338
  declare function TriggerGaugeHost(props: TriggerGaugeProps): react.JSX.Element;
339
339
 
340
- export { AchievementRow, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, type ConfirmDialogProps, ContentGutter, ControlSchemePicker, type ControlSchemePickerProps, CornerActionButtons, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LOCKED_BADGE_COLOR, LabeledDropdown, type LabeledDropdownOption, MONO_FONT, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, type OverlayCardColors, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, StatRow, StatSection, TriggerGaugeHost, type TriggerGaugeProps, getLabeledDropdownContentHeight, getOverlayCardColors, loadSkiaWeb, overlayStyles, useAutoAlign, usePopoverHost };
340
+ interface UpdateDialogProps {
341
+ rotation?: ViewRotation;
342
+ autoCheck?: boolean;
343
+ autoPrompt?: boolean;
344
+ onError?: (message: string) => void;
345
+ }
346
+ declare function UpdateDialog({ rotation, autoCheck, autoPrompt, onError }: UpdateDialogProps): react.JSX.Element;
347
+
348
+ export { AchievementRow, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, ConfirmDialog, type ConfirmDialogProps, ContentGutter, ControlSchemePicker, type ControlSchemePickerProps, CornerActionButtons, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LOCKED_BADGE_COLOR, LabeledDropdown, type LabeledDropdownOption, MONO_FONT, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, type OverlayCardColors, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, StatRow, StatSection, TriggerGaugeHost, type TriggerGaugeProps, UpdateDialog, type UpdateDialogProps, getLabeledDropdownContentHeight, getOverlayCardColors, loadSkiaWeb, overlayStyles, useAutoAlign, usePopoverHost };
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_react4.useLayoutEffect)(() => {
31
+ (0, import_react5.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_react4.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
37
+ const centerDeg = (0, import_react5.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_react4.useState)(litIndices);
41
+ const [transitionFrom, setTransitionFrom] = (0, import_react5.useState)(litIndices);
42
42
  const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
43
- const lastKeyRef = (0, import_react4.useRef)(litIndicesKey);
44
- const lastIndicesRef = (0, import_react4.useRef)(litIndices);
43
+ const lastKeyRef = (0, import_react5.useRef)(litIndicesKey);
44
+ const lastIndicesRef = (0, import_react5.useRef)(litIndices);
45
45
  const selectionProgress = (0, import_react_native_reanimated.useSharedValue)(1);
46
- (0, import_react4.useLayoutEffect)(() => {
46
+ (0, import_react5.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) });
@@ -125,13 +125,13 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
125
125
  /* @__PURE__ */ (0, import_jsx_runtime8.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_core5, import_react4, import_react_native9, import_react_native_reanimated, import_jsx_runtime8, 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, styles7;
128
+ var import_react_native_skia, import_core5, import_react5, import_react_native9, import_react_native_reanimated, import_jsx_runtime8, 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, styles7;
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_core5 = require("@tastic/core");
134
- import_react4 = require("react");
134
+ import_react5 = require("react");
135
135
  import_react_native9 = require("react-native");
136
136
  import_react_native_reanimated = require("react-native-reanimated");
137
137
  import_jsx_runtime8 = require("react/jsx-runtime");
@@ -179,6 +179,7 @@ __export(index_exports, {
179
179
  StatRow: () => StatRow,
180
180
  StatSection: () => StatSection,
181
181
  TriggerGaugeHost: () => TriggerGaugeHost,
182
+ UpdateDialog: () => UpdateDialog,
182
183
  getLabeledDropdownContentHeight: () => getLabeledDropdownContentHeight,
183
184
  getOverlayCardColors: () => getOverlayCardColors,
184
185
  loadSkiaWeb: () => loadSkiaWeb,
@@ -257,11 +258,18 @@ var styles = import_react_native.StyleSheet.create({
257
258
  });
258
259
 
259
260
  // src/BaseSettingsDialog.tsx
261
+ var import_auto_paper3 = require("@rific/auto-paper");
262
+ var import_feedback_press2 = require("@rific/feedback-press");
263
+ var import_updater = require("@rific/updater");
264
+ var import_core2 = require("@tastic/core");
265
+ var import_react2 = require("react");
266
+ var import_react_native4 = require("react-native");
267
+ var import_react_native_paper3 = require("react-native-paper");
268
+
269
+ // src/ConfirmDialog.tsx
260
270
  var import_auto_paper2 = require("@rific/auto-paper");
261
271
  var import_feedback_press = require("@rific/feedback-press");
262
- var import_updater = require("@rific/updater");
263
272
  var import_core = require("@tastic/core");
264
- var import_react = require("react");
265
273
  var import_react_native3 = require("react-native");
266
274
  var import_react_native_paper2 = require("react-native-paper");
267
275
 
@@ -296,56 +304,164 @@ var overlayStyles = import_react_native2.StyleSheet.create({
296
304
  }
297
305
  });
298
306
 
299
- // src/BaseSettingsDialog.tsx
307
+ // src/ConfirmDialog.tsx
300
308
  var import_jsx_runtime2 = require("react/jsx-runtime");
309
+ function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
310
+ const { dark, colors } = (0, import_auto_paper2.useAutoPaperTheme)();
311
+ if (!visible) return null;
312
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
313
+ const accent = destructive ? colors.danger : colors.primary;
314
+ const onAccent = destructive ? colors.onDanger : colors.onPrimary;
315
+ const showCancel = cancelLabel !== void 0 && onCancel !== void 0;
316
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [overlayStyles.overlay, styles2.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [overlayStyles.card, styles2.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core.toRotationStyle)(rotation)], children: [
317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: 64, color: accent }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineLarge", style: [styles2.title, { color: accent }], children: title }),
319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: [styles2.body, { color: fg }], children: message }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.actions, children: [
321
+ showCancel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: styles2.actionButton, children: cancelLabel }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: showCancel ? styles2.actionButton : styles2.actionButtonSingle, buttonColor: accent, textColor: onAccent, children: confirmLabel })
323
+ ] })
324
+ ] }) }) });
325
+ }
326
+ var styles2 = import_react_native3.StyleSheet.create({
327
+ // minWidth (not flexBasis) is each button's real floor - whenever both fit side by side, flexGrow
328
+ // still splits any leftover width evenly beyond that floor, same visual result as a plain flex: 1
329
+ // on a roomy screen, but a button can never be squeezed narrower than 128, only wrap to its own
330
+ // row instead (see actions' flexWrap below). This used to be flexBasis: 128, which measured
331
+ // correctly on web but rendered every button roughly SQUARE on iOS - confirmed live (see the
332
+ // "Quit Match?" Cancel/Quit buttons and the single-button "OK" dialogs both coming out ~128pt
333
+ // tall instead of wide). Root cause: react-native-paper's Button renders its Surface as two
334
+ // nested views on iOS only (Surface.tsx's SurfaceIOS, for shadow rendering) - only a curated
335
+ // allowlist of style keys (flex/flexGrow/flexShrink/width/height/position/... - notably NOT
336
+ // flexBasis) reaches the outer view, which is the real flex item inside this row; every other
337
+ // key, flexBasis included, falls through to an inner view whose own unspecified flexDirection
338
+ // defaults to column. flexGrow: 1 landed on the (row-context) outer view and grew width
339
+ // correctly; flexBasis: 128 landed on the (column-context) inner view instead, where a flexBasis
340
+ // sets HEIGHT, not width. minWidth isn't axis-relative like flexBasis, so it constrains width
341
+ // correctly regardless of which of the two views it ends up on. react-native-paper's Button also
342
+ // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
343
+ // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
344
+ // fixable by just giving the row more width, until confirmed live that these two-word labels
345
+ // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
346
+ // width fix below provides, on a narrow enough screen.
347
+ actionButton: {
348
+ flexGrow: 1,
349
+ minWidth: 128
350
+ },
351
+ // A lone confirm button (no cancel - BaseSettingsDialog's info-only "OK" dialogs, e.g. the
352
+ // update-check "no update found" message) has nothing left to share actions' width with, so
353
+ // actionButton's own flexGrow: 1 would stretch it across the entire row instead of reading as a
354
+ // normal button. Same 128 floor, no flexGrow, so it stays at that natural size instead of filling
355
+ // the card - centered by actions' own justifyContent below.
356
+ actionButtonSingle: {
357
+ minWidth: 128
358
+ },
359
+ actions: {
360
+ flexDirection: "row",
361
+ flexWrap: "wrap",
362
+ gap: 12,
363
+ justifyContent: "center",
364
+ width: "100%"
365
+ },
366
+ body: {
367
+ textAlign: "center"
368
+ },
369
+ // Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
370
+ // padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
371
+ // card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
372
+ // instead of stretching, so the card (and therefore actions below) can end up narrower than the
373
+ // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
374
+ card: {
375
+ width: "100%"
376
+ },
377
+ // Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
378
+ // from the screen edges now that it reliably stretches to fill its full width allowance (see
379
+ // card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
380
+ // screen narrower than maxWidth's own 360.
381
+ overlay: {
382
+ paddingHorizontal: 24
383
+ },
384
+ // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
385
+ // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
386
+ // line inside that block still defaults to left-aligned without this.
387
+ title: {
388
+ fontWeight: "bold",
389
+ textAlign: "center"
390
+ }
391
+ });
392
+
393
+ // src/useUpdateConfirmBridge.ts
394
+ var import_react = require("react");
395
+ function useUpdateConfirmBridge() {
396
+ const [manifest, setManifest] = (0, import_react.useState)(null);
397
+ const resolveRef = (0, import_react.useRef)(null);
398
+ const onConfirm = (0, import_react.useCallback)(
399
+ (next) => new Promise((resolve) => {
400
+ resolveRef.current = resolve;
401
+ setManifest(next);
402
+ }),
403
+ []
404
+ );
405
+ const respond = (0, import_react.useCallback)((confirmed) => {
406
+ setManifest(null);
407
+ resolveRef.current?.(confirmed);
408
+ resolveRef.current = null;
409
+ }, []);
410
+ return { manifest, onConfirm, respond };
411
+ }
412
+
413
+ // src/BaseSettingsDialog.tsx
414
+ var import_jsx_runtime3 = require("react/jsx-runtime");
301
415
  function SettingIcon({ source, color, containerColor }) {
302
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: [styles2.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source, size: 18, color }) });
416
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source, size: 18, color }) });
303
417
  }
304
418
  function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
305
- const { dark, colors } = (0, import_auto_paper2.useAutoPaperTheme)();
306
- const ambientRotation = (0, import_core.useRotation)();
419
+ const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
420
+ const ambientRotation = (0, import_core2.useRotation)();
307
421
  const rotation = rotationOverride ?? ambientRotation;
308
- const isTouchPrimary = (0, import_core.useIsTouchPrimaryDevice)();
309
- const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press.useHapticSettings)();
310
- const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press.useSoundSettings)();
311
- const sound = (0, import_react.useContext)(import_feedback_press.SoundContext);
312
- const { forceShort: forceHapticFeedback } = (0, import_feedback_press.useVibration)();
313
- const [infoMessage, setInfoMessage] = (0, import_react.useState)(null);
422
+ const isTouchPrimary = (0, import_core2.useIsTouchPrimaryDevice)();
423
+ const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press2.useHapticSettings)();
424
+ const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
425
+ const sound = (0, import_react2.useContext)(import_feedback_press2.SoundContext);
426
+ const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
427
+ const [infoMessage, setInfoMessage] = (0, import_react2.useState)(null);
428
+ const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
314
429
  const { check, checking, updateReady } = (0, import_updater.useUpdater)({
315
430
  autoCheck: false,
316
431
  autoPrompt: false,
432
+ onConfirm: onUpdateConfirm,
317
433
  onError: onUpdateError,
318
434
  onInfo: (title, message) => setInfoMessage({ title, message })
319
435
  });
436
+ const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
320
437
  const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
321
- const showEdgeGuard = import_react_native3.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
322
- const showHaptics = import_react_native3.Platform.OS !== "web" && !hideHaptics;
438
+ const showEdgeGuard = import_react_native4.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
439
+ const showHaptics = import_react_native4.Platform.OS !== "web" && !hideHaptics;
323
440
  const showSound = !hideSound;
324
441
  const showAppearance = !hideAppearance;
325
- const showUpdateCheck = import_react_native3.Platform.OS !== "web" && !hideUpdateCheck;
326
- const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
327
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
328
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_auto_paper2.Dialog, { visible, onDismiss, style: [styles2.dialog, (0, import_core.toRotationStyle)(rotation)], children: [
329
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.Dialog.Title, { children: "Settings" }),
330
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.Dialog.ScrollArea, { style: styles2.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.ScrollView, { contentContainerStyle: styles2.content, showsVerticalScrollIndicator: false, children: [
331
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.toggleGroup, children: [
332
- 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_native3.View, { style: styles2.toggleContent, children: [
333
- /* @__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 }),
334
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.flexShrink, children: [
335
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
336
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
442
+ const showUpdateCheck = import_react_native4.Platform.OS !== "web" && !hideUpdateCheck;
443
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
444
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_auto_paper3.Dialog, { visible, onDismiss, style: [styles3.dialog, (0, import_core2.toRotationStyle)(rotation)], children: [
445
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.Dialog.Title, { children: "Settings" }),
446
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.Dialog.ScrollArea, { style: styles3.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.ScrollView, { contentContainerStyle: styles3.content, showsVerticalScrollIndicator: false, children: [
447
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleGroup, children: [
448
+ showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles3.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
449
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
450
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
451
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
337
453
  ] })
338
454
  ] }) }),
339
- 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_native3.View, { style: styles2.toggleContent, children: [
340
- /* @__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 }),
341
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.flexShrink, children: [
342
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
343
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
455
+ showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles3.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
456
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
457
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
458
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
344
460
  ] })
345
461
  ] }) }),
346
- (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.toggleRow, children: [
347
- showSound && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
348
- import_feedback_press.TouchableRipple,
462
+ (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleRow, children: [
463
+ showSound && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
464
+ import_feedback_press2.TouchableRipple,
349
465
  {
350
466
  soundDisabled: true,
351
467
  onPress: () => {
@@ -353,16 +469,16 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
353
469
  setSoundSettings({ enabled });
354
470
  if (enabled) sound.selection?.();
355
471
  },
356
- style: styles2.toggleButtonInRow,
472
+ style: styles3.toggleButtonInRow,
357
473
  accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
358
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.toggleContent, children: [
359
- /* @__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 }),
360
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
474
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
361
477
  ] })
362
478
  }
363
479
  ),
364
- showHaptics && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
365
- import_feedback_press.TouchableRipple,
480
+ showHaptics && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
481
+ import_feedback_press2.TouchableRipple,
366
482
  {
367
483
  hapticDisabled: true,
368
484
  onPress: () => {
@@ -370,40 +486,36 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
370
486
  setHapticSettings({ vibrate });
371
487
  if (vibrate) forceHapticFeedback();
372
488
  },
373
- style: styles2.toggleButtonInRow,
489
+ style: styles3.toggleButtonInRow,
374
490
  accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
375
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.toggleContent, children: [
376
- /* @__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 }),
377
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
491
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
492
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
493
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
378
494
  ] })
379
495
  }
380
496
  )
381
497
  ] })
382
498
  ] }),
383
- showAppearance && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.section, children: [
384
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
385
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.AutoAppearancePicker, { showLabels: false })
499
+ showAppearance && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
500
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "labelMedium", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.AutoAppearancePicker, { showLabels: false })
386
502
  ] }),
387
503
  children,
388
- showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.section, children: [
389
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_paper2.Text, { variant: "labelSmall", style: [styles2.sectionLabel, { color: colors.onSurfaceVariant }], children: [
504
+ showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
505
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_paper3.Text, { variant: "labelSmall", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: [
390
506
  "VERSION ",
391
507
  version,
392
508
  updateReady ? " \xB7 UPDATE READY" : ""
393
509
  ] }),
394
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
510
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
395
511
  ] })
396
512
  ] }) })
397
513
  ] }),
398
- infoMessage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.View, { style: overlayStyles.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: [overlayStyles.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core.toRotationStyle)(rotation)], children: [
399
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "information-outline", size: 64, color: colors.secondary }),
400
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineLarge", style: [styles2.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
401
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: [styles2.overlayBody, { color: fg }], children: infoMessage.message }),
402
- /* @__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" })
403
- ] }) }) })
514
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfirmDialog, { visible: !!updateManifest, title: "Update Available", message: updateManifestDate ? `Released ${updateManifestDate.toLocaleDateString()} at ${updateManifestDate.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation, onConfirm: () => respondToUpdateConfirm(true), onCancel: () => respondToUpdateConfirm(false) }),
515
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConfirmDialog, { visible: !!infoMessage, title: infoMessage?.title ?? "", message: infoMessage?.message ?? "", confirmLabel: "OK", icon: "information-outline", rotation, onConfirm: () => setInfoMessage(null) })
404
516
  ] });
405
517
  }
406
- var styles2 = import_react_native3.StyleSheet.create({
518
+ var styles3 = import_react_native4.StyleSheet.create({
407
519
  content: {
408
520
  gap: 24,
409
521
  paddingVertical: 20
@@ -425,9 +537,6 @@ var styles2 = import_react_native3.StyleSheet.create({
425
537
  justifyContent: "center",
426
538
  width: 36
427
539
  },
428
- overlayBody: { textAlign: "center" },
429
- overlayButton: { width: 160 },
430
- overlayTitle: { fontWeight: "bold" },
431
540
  scrollArea: {
432
541
  marginBottom: 0
433
542
  },
@@ -477,89 +586,15 @@ var styles2 = import_react_native3.StyleSheet.create({
477
586
  var import_auto_paper4 = require("@rific/auto-paper");
478
587
  var import_feedback_press3 = require("@rific/feedback-press");
479
588
  var import_core3 = require("@tastic/core");
480
- var import_react2 = require("react");
589
+ var import_react3 = require("react");
481
590
  var import_react_native5 = require("react-native");
482
591
  var import_react_native_paper4 = require("react-native-paper");
483
-
484
- // src/ConfirmDialog.tsx
485
- var import_auto_paper3 = require("@rific/auto-paper");
486
- var import_feedback_press2 = require("@rific/feedback-press");
487
- var import_core2 = require("@tastic/core");
488
- var import_react_native4 = require("react-native");
489
- var import_react_native_paper3 = require("react-native-paper");
490
- var import_jsx_runtime3 = require("react/jsx-runtime");
491
- function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
492
- const { dark, colors } = (0, import_auto_paper3.useAutoPaperTheme)();
493
- if (!visible) return null;
494
- const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
495
- const accent = destructive ? colors.danger : colors.primary;
496
- const onAccent = destructive ? colors.onDanger : colors.onPrimary;
497
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [overlayStyles.overlay, styles3.overlay], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: [overlayStyles.card, styles3.card, { backgroundColor: cardBg, borderColor: cardBorder }, (0, import_core2.toRotationStyle)(rotation)], children: [
498
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: icon, size: 64, color: accent }),
499
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "headlineLarge", style: [styles3.title, { color: accent }], children: title }),
500
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.body, { color: fg }], children: message }),
501
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.actions, children: [
502
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: onCancel, style: styles3.actionButton, children: cancelLabel }),
503
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "contained", onPress: onConfirm, style: styles3.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
504
- ] })
505
- ] }) }) });
506
- }
507
- var styles3 = import_react_native4.StyleSheet.create({
508
- // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
509
- // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
510
- // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
511
- // wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
512
- // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
513
- // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
514
- // fixable by just giving the row more width, until confirmed live that these two-word labels
515
- // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
516
- // width fix below provides, on a narrow enough screen. flexBasis is the real fix: past a certain
517
- // width there's no side-by-side arrangement that fits both buttons at their natural label size,
518
- // so the layout has to give (stack) rather than the label (truncate).
519
- actionButton: {
520
- flexBasis: 128,
521
- flexGrow: 1
522
- },
523
- actions: {
524
- flexDirection: "row",
525
- flexWrap: "wrap",
526
- gap: 12,
527
- width: "100%"
528
- },
529
- body: {
530
- textAlign: "center"
531
- },
532
- // Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
533
- // padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
534
- // card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
535
- // instead of stretching, so the card (and therefore actions below) can end up narrower than the
536
- // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
537
- card: {
538
- width: "100%"
539
- },
540
- // Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
541
- // from the screen edges now that it reliably stretches to fill its full width allowance (see
542
- // card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
543
- // screen narrower than maxWidth's own 360.
544
- overlay: {
545
- paddingHorizontal: 24
546
- },
547
- // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
548
- // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
549
- // line inside that block still defaults to left-aligned without this.
550
- title: {
551
- fontWeight: "bold",
552
- textAlign: "center"
553
- }
554
- });
555
-
556
- // src/BaseStatsScreen.tsx
557
592
  var import_jsx_runtime4 = require("react/jsx-runtime");
558
593
  function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", titleVariant = "headlineSmall", onBack, insets, onReset, resetLabel = "Reset All Stats", resetConfirmTitle = "Reset Everything?", resetConfirmBody = "This permanently erases all stats and achievements. This cannot be undone.", fg: fgOverride, bg: bgOverride, children }) {
559
594
  const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
560
595
  const ambientRotation = (0, import_core3.useRotation)();
561
596
  const rotation = rotationOverride ?? ambientRotation;
562
- const [confirmResetVisible, setConfirmResetVisible] = (0, import_react2.useState)(false);
597
+ const [confirmResetVisible, setConfirmResetVisible] = (0, import_react3.useState)(false);
563
598
  const showReset = !!onReset;
564
599
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
565
600
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
@@ -728,11 +763,11 @@ var styles6 = import_react_native8.StyleSheet.create({
728
763
  });
729
764
 
730
765
  // src/SkiaGate.tsx
731
- var import_react3 = require("react");
766
+ var import_react4 = require("react");
732
767
  var import_jsx_runtime7 = require("react/jsx-runtime");
733
768
  function SkiaGate({ getComponent, componentProps, fallback = null }) {
734
- const [Component, setComponent] = (0, import_react3.useState)(null);
735
- (0, import_react3.useEffect)(() => {
769
+ const [Component, setComponent] = (0, import_react4.useState)(null);
770
+ (0, import_react4.useEffect)(() => {
736
771
  let cancelled = false;
737
772
  getComponent().then((mod) => {
738
773
  if (!cancelled) setComponent(() => mod.default);
@@ -753,7 +788,7 @@ function TriggerGaugeHost(props) {
753
788
  }
754
789
 
755
790
  // src/useAutoAlign.ts
756
- var import_react5 = require("react");
791
+ var import_react6 = require("react");
757
792
  var import_react_native10 = require("react-native");
758
793
  var EDGE_MARGIN = 12;
759
794
  function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
@@ -766,18 +801,18 @@ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
766
801
  return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
767
802
  }
768
803
  function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
769
- const triggerRef = (0, import_react5.useRef)(null);
804
+ const triggerRef = (0, import_react6.useRef)(null);
770
805
  const { width: windowWidth, height: windowHeight } = (0, import_react_native10.useWindowDimensions)();
771
- const [align, setAlign] = (0, import_react5.useState)("center");
772
- const [verticalAlign, setVerticalAlign] = (0, import_react5.useState)("below");
773
- const [maxHeight, setMaxHeight] = (0, import_react5.useState)(contentHeight);
774
- const [measured, setMeasured] = (0, import_react5.useState)(false);
775
- const [prevOpen, setPrevOpen] = (0, import_react5.useState)(open);
806
+ const [align, setAlign] = (0, import_react6.useState)("center");
807
+ const [verticalAlign, setVerticalAlign] = (0, import_react6.useState)("below");
808
+ const [maxHeight, setMaxHeight] = (0, import_react6.useState)(contentHeight);
809
+ const [measured, setMeasured] = (0, import_react6.useState)(false);
810
+ const [prevOpen, setPrevOpen] = (0, import_react6.useState)(open);
776
811
  if (open !== prevOpen) {
777
812
  setPrevOpen(open);
778
813
  if (open) setMeasured(false);
779
814
  }
780
- const measure = (0, import_react5.useCallback)(() => {
815
+ const measure = (0, import_react6.useCallback)(() => {
781
816
  triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
782
817
  const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
783
818
  const centerX = x + triggerWidth / 2;
@@ -794,7 +829,7 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
794
829
  setMeasured(true);
795
830
  });
796
831
  }, [contentWidth, contentHeight, windowWidth, windowHeight, rotation]);
797
- (0, import_react5.useEffect)(() => {
832
+ (0, import_react6.useEffect)(() => {
798
833
  if (open) measure();
799
834
  }, [open, measure]);
800
835
  return { align, maxHeight, measured, triggerRef, verticalAlign };
@@ -1402,14 +1437,24 @@ var styles16 = import_react_native21.StyleSheet.create({
1402
1437
  }
1403
1438
  });
1404
1439
 
1440
+ // src/UpdateDialog.tsx
1441
+ var import_updater2 = require("@rific/updater");
1442
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1443
+ function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1444
+ const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1445
+ (0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
1446
+ const date = manifest ? new Date(manifest.createdAt) : null;
1447
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ConfirmDialog, { visible: !!manifest, title: "Update Available", message: date ? `Released ${date.toLocaleDateString()} at ${date.toLocaleTimeString()}` : "", confirmLabel: "Restart", cancelLabel: "Later", icon: "update", rotation, onConfirm: () => respond(true), onCancel: () => respond(false) });
1448
+ }
1449
+
1405
1450
  // src/usePopoverHost.ts
1406
- var import_react6 = require("react");
1451
+ var import_react7 = require("react");
1407
1452
  function usePopoverHost() {
1408
- const [openId, setOpenId] = (0, import_react6.useState)(null);
1409
- const toggle = (0, import_react6.useCallback)((id) => {
1453
+ const [openId, setOpenId] = (0, import_react7.useState)(null);
1454
+ const toggle = (0, import_react7.useCallback)((id) => {
1410
1455
  setOpenId((prev) => prev === id ? null : id);
1411
1456
  }, []);
1412
- const close = (0, import_react6.useCallback)(() => setOpenId(null), []);
1457
+ const close = (0, import_react7.useCallback)(() => setOpenId(null), []);
1413
1458
  return { openId, toggle, close };
1414
1459
  }
1415
1460
  // Annotate the CommonJS export names for ESM import in node:
@@ -1436,6 +1481,7 @@ function usePopoverHost() {
1436
1481
  StatRow,
1437
1482
  StatSection,
1438
1483
  TriggerGaugeHost,
1484
+ UpdateDialog,
1439
1485
  getLabeledDropdownContentHeight,
1440
1486
  getOverlayCardColors,
1441
1487
  loadSkiaWeb,