@tastic/hud 0.8.3 → 0.8.4

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,145 @@ 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
+ 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: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: 64, color: accent }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "headlineLarge", style: [styles2.title, { color: accent }], children: title }),
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "bodyLarge", style: [styles2.body, { color: fg }], children: message }),
319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native3.View, { style: styles2.actions, children: [
320
+ cancelLabel !== void 0 && onCancel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: onCancel, style: styles2.actionButton, children: cancelLabel }),
321
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "contained", onPress: onConfirm, style: styles2.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
322
+ ] })
323
+ ] }) }) });
324
+ }
325
+ var styles2 = import_react_native3.StyleSheet.create({
326
+ // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
327
+ // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
328
+ // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
329
+ // wrap to its own row instead (see actions' flexWrap below). react-native-paper's Button
330
+ // hardcodes numberOfLines={1} on its label with no override, so a button squeezed under its
331
+ // label's natural width silently ellipsizes ("Cancel" -> "Can...") instead of wrapping - assumed
332
+ // fixable by just giving the row more width, until confirmed live that these two-word labels
333
+ // (Cancel/Reset/New Game) can still lose the truncation fight even with the extra room card's own
334
+ // width fix below provides, on a narrow enough screen. flexBasis is the real fix: past a certain
335
+ // width there's no side-by-side arrangement that fits both buttons at their natural label size,
336
+ // so the layout has to give (stack) rather than the label (truncate).
337
+ actionButton: {
338
+ flexBasis: 128,
339
+ flexGrow: 1
340
+ },
341
+ actions: {
342
+ flexDirection: "row",
343
+ flexWrap: "wrap",
344
+ gap: 12,
345
+ width: "100%"
346
+ },
347
+ body: {
348
+ textAlign: "center"
349
+ },
350
+ // Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
351
+ // padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
352
+ // card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
353
+ // instead of stretching, so the card (and therefore actions below) can end up narrower than the
354
+ // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
355
+ card: {
356
+ width: "100%"
357
+ },
358
+ // Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
359
+ // from the screen edges now that it reliably stretches to fill its full width allowance (see
360
+ // card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
361
+ // screen narrower than maxWidth's own 360.
362
+ overlay: {
363
+ paddingHorizontal: 24
364
+ },
365
+ // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
366
+ // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
367
+ // line inside that block still defaults to left-aligned without this.
368
+ title: {
369
+ fontWeight: "bold",
370
+ textAlign: "center"
371
+ }
372
+ });
373
+
374
+ // src/useUpdateConfirmBridge.ts
375
+ var import_react = require("react");
376
+ function useUpdateConfirmBridge() {
377
+ const [manifest, setManifest] = (0, import_react.useState)(null);
378
+ const resolveRef = (0, import_react.useRef)(null);
379
+ const onConfirm = (0, import_react.useCallback)(
380
+ (next) => new Promise((resolve) => {
381
+ resolveRef.current = resolve;
382
+ setManifest(next);
383
+ }),
384
+ []
385
+ );
386
+ const respond = (0, import_react.useCallback)((confirmed) => {
387
+ setManifest(null);
388
+ resolveRef.current?.(confirmed);
389
+ resolveRef.current = null;
390
+ }, []);
391
+ return { manifest, onConfirm, respond };
392
+ }
393
+
394
+ // src/BaseSettingsDialog.tsx
395
+ var import_jsx_runtime3 = require("react/jsx-runtime");
301
396
  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 }) });
397
+ 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
398
  }
304
399
  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)();
400
+ const { colors } = (0, import_auto_paper3.useAutoPaperTheme)();
401
+ const ambientRotation = (0, import_core2.useRotation)();
307
402
  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);
403
+ const isTouchPrimary = (0, import_core2.useIsTouchPrimaryDevice)();
404
+ const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press2.useHapticSettings)();
405
+ const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
406
+ const sound = (0, import_react2.useContext)(import_feedback_press2.SoundContext);
407
+ const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
408
+ const [infoMessage, setInfoMessage] = (0, import_react2.useState)(null);
409
+ const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
314
410
  const { check, checking, updateReady } = (0, import_updater.useUpdater)({
315
411
  autoCheck: false,
316
412
  autoPrompt: false,
413
+ onConfirm: onUpdateConfirm,
317
414
  onError: onUpdateError,
318
415
  onInfo: (title, message) => setInfoMessage({ title, message })
319
416
  });
417
+ const updateManifestDate = updateManifest ? new Date(updateManifest.createdAt) : null;
320
418
  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;
419
+ const showEdgeGuard = import_react_native4.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
420
+ const showHaptics = import_react_native4.Platform.OS !== "web" && !hideHaptics;
323
421
  const showSound = !hideSound;
324
422
  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" })
423
+ const showUpdateCheck = import_react_native4.Platform.OS !== "web" && !hideUpdateCheck;
424
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_auto_paper3.Dialog, { visible, onDismiss, style: [styles3.dialog, (0, import_core2.toRotationStyle)(rotation)], children: [
426
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.Dialog.Title, { children: "Settings" }),
427
+ /* @__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: [
428
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleGroup, children: [
429
+ 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: [
430
+ /* @__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 }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
433
+ /* @__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
434
  ] })
338
435
  ] }) }),
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" })
436
+ 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: [
437
+ /* @__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 }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.flexShrink, children: [
439
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
440
+ /* @__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
441
  ] })
345
442
  ] }) }),
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,
443
+ (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleRow, children: [
444
+ showSound && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
445
+ import_feedback_press2.TouchableRipple,
349
446
  {
350
447
  soundDisabled: true,
351
448
  onPress: () => {
@@ -353,16 +450,16 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
353
450
  setSoundSettings({ enabled });
354
451
  if (enabled) sound.selection?.();
355
452
  },
356
- style: styles2.toggleButtonInRow,
453
+ style: styles3.toggleButtonInRow,
357
454
  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" })
455
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
456
+ /* @__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 }),
457
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
361
458
  ] })
362
459
  }
363
460
  ),
364
- showHaptics && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
365
- import_feedback_press.TouchableRipple,
461
+ showHaptics && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
462
+ import_feedback_press2.TouchableRipple,
366
463
  {
367
464
  hapticDisabled: true,
368
465
  onPress: () => {
@@ -370,40 +467,36 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
370
467
  setHapticSettings({ vibrate });
371
468
  if (vibrate) forceHapticFeedback();
372
469
  },
373
- style: styles2.toggleButtonInRow,
470
+ style: styles3.toggleButtonInRow,
374
471
  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" })
472
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.toggleContent, children: [
473
+ /* @__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 }),
474
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
378
475
  ] })
379
476
  }
380
477
  )
381
478
  ] })
382
479
  ] }),
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 })
480
+ showAppearance && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "labelMedium", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_auto_paper3.AutoAppearancePicker, { showLabels: false })
386
483
  ] }),
387
484
  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: [
485
+ showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: styles3.section, children: [
486
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_paper3.Text, { variant: "labelSmall", style: [styles3.sectionLabel, { color: colors.onSurfaceVariant }], children: [
390
487
  "VERSION ",
391
488
  version,
392
489
  updateReady ? " \xB7 UPDATE READY" : ""
393
490
  ] }),
394
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
491
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
395
492
  ] })
396
493
  ] }) })
397
494
  ] }),
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
- ] }) }) })
495
+ /* @__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) }),
496
+ /* @__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
497
  ] });
405
498
  }
406
- var styles2 = import_react_native3.StyleSheet.create({
499
+ var styles3 = import_react_native4.StyleSheet.create({
407
500
  content: {
408
501
  gap: 24,
409
502
  paddingVertical: 20
@@ -425,9 +518,6 @@ var styles2 = import_react_native3.StyleSheet.create({
425
518
  justifyContent: "center",
426
519
  width: 36
427
520
  },
428
- overlayBody: { textAlign: "center" },
429
- overlayButton: { width: 160 },
430
- overlayTitle: { fontWeight: "bold" },
431
521
  scrollArea: {
432
522
  marginBottom: 0
433
523
  },
@@ -477,89 +567,15 @@ var styles2 = import_react_native3.StyleSheet.create({
477
567
  var import_auto_paper4 = require("@rific/auto-paper");
478
568
  var import_feedback_press3 = require("@rific/feedback-press");
479
569
  var import_core3 = require("@tastic/core");
480
- var import_react2 = require("react");
570
+ var import_react3 = require("react");
481
571
  var import_react_native5 = require("react-native");
482
572
  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
573
  var import_jsx_runtime4 = require("react/jsx-runtime");
558
574
  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
575
  const { dark, colors } = (0, import_auto_paper4.useAutoPaperTheme)();
560
576
  const ambientRotation = (0, import_core3.useRotation)();
561
577
  const rotation = rotationOverride ?? ambientRotation;
562
- const [confirmResetVisible, setConfirmResetVisible] = (0, import_react2.useState)(false);
578
+ const [confirmResetVisible, setConfirmResetVisible] = (0, import_react3.useState)(false);
563
579
  const showReset = !!onReset;
564
580
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
565
581
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
@@ -728,11 +744,11 @@ var styles6 = import_react_native8.StyleSheet.create({
728
744
  });
729
745
 
730
746
  // src/SkiaGate.tsx
731
- var import_react3 = require("react");
747
+ var import_react4 = require("react");
732
748
  var import_jsx_runtime7 = require("react/jsx-runtime");
733
749
  function SkiaGate({ getComponent, componentProps, fallback = null }) {
734
- const [Component, setComponent] = (0, import_react3.useState)(null);
735
- (0, import_react3.useEffect)(() => {
750
+ const [Component, setComponent] = (0, import_react4.useState)(null);
751
+ (0, import_react4.useEffect)(() => {
736
752
  let cancelled = false;
737
753
  getComponent().then((mod) => {
738
754
  if (!cancelled) setComponent(() => mod.default);
@@ -753,7 +769,7 @@ function TriggerGaugeHost(props) {
753
769
  }
754
770
 
755
771
  // src/useAutoAlign.ts
756
- var import_react5 = require("react");
772
+ var import_react6 = require("react");
757
773
  var import_react_native10 = require("react-native");
758
774
  var EDGE_MARGIN = 12;
759
775
  function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
@@ -766,18 +782,18 @@ function rotateRect(x, y, width, height, rotation, windowWidth, windowHeight) {
766
782
  return { x: cx + newDx - newWidth / 2, y: cy + newDy - newHeight / 2, width: newWidth, height: newHeight };
767
783
  }
768
784
  function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
769
- const triggerRef = (0, import_react5.useRef)(null);
785
+ const triggerRef = (0, import_react6.useRef)(null);
770
786
  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);
787
+ const [align, setAlign] = (0, import_react6.useState)("center");
788
+ const [verticalAlign, setVerticalAlign] = (0, import_react6.useState)("below");
789
+ const [maxHeight, setMaxHeight] = (0, import_react6.useState)(contentHeight);
790
+ const [measured, setMeasured] = (0, import_react6.useState)(false);
791
+ const [prevOpen, setPrevOpen] = (0, import_react6.useState)(open);
776
792
  if (open !== prevOpen) {
777
793
  setPrevOpen(open);
778
794
  if (open) setMeasured(false);
779
795
  }
780
- const measure = (0, import_react5.useCallback)(() => {
796
+ const measure = (0, import_react6.useCallback)(() => {
781
797
  triggerRef.current?.measureInWindow((rawX, rawY, rawWidth, rawHeight) => {
782
798
  const { x, y, width: triggerWidth, height: triggerHeight } = rotateRect(rawX, rawY, rawWidth, rawHeight, rotation, windowWidth, windowHeight);
783
799
  const centerX = x + triggerWidth / 2;
@@ -794,7 +810,7 @@ function useAutoAlign(open, contentWidth, contentHeight, rotation = 0) {
794
810
  setMeasured(true);
795
811
  });
796
812
  }, [contentWidth, contentHeight, windowWidth, windowHeight, rotation]);
797
- (0, import_react5.useEffect)(() => {
813
+ (0, import_react6.useEffect)(() => {
798
814
  if (open) measure();
799
815
  }, [open, measure]);
800
816
  return { align, maxHeight, measured, triggerRef, verticalAlign };
@@ -1402,14 +1418,24 @@ var styles16 = import_react_native21.StyleSheet.create({
1402
1418
  }
1403
1419
  });
1404
1420
 
1421
+ // src/UpdateDialog.tsx
1422
+ var import_updater2 = require("@rific/updater");
1423
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1424
+ function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onError }) {
1425
+ const { manifest, onConfirm, respond } = useUpdateConfirmBridge();
1426
+ (0, import_updater2.useUpdater)({ autoCheck, autoPrompt, onConfirm, onError });
1427
+ const date = manifest ? new Date(manifest.createdAt) : null;
1428
+ 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) });
1429
+ }
1430
+
1405
1431
  // src/usePopoverHost.ts
1406
- var import_react6 = require("react");
1432
+ var import_react7 = require("react");
1407
1433
  function usePopoverHost() {
1408
- const [openId, setOpenId] = (0, import_react6.useState)(null);
1409
- const toggle = (0, import_react6.useCallback)((id) => {
1434
+ const [openId, setOpenId] = (0, import_react7.useState)(null);
1435
+ const toggle = (0, import_react7.useCallback)((id) => {
1410
1436
  setOpenId((prev) => prev === id ? null : id);
1411
1437
  }, []);
1412
- const close = (0, import_react6.useCallback)(() => setOpenId(null), []);
1438
+ const close = (0, import_react7.useCallback)(() => setOpenId(null), []);
1413
1439
  return { openId, toggle, close };
1414
1440
  }
1415
1441
  // Annotate the CommonJS export names for ESM import in node:
@@ -1436,6 +1462,7 @@ function usePopoverHost() {
1436
1462
  StatRow,
1437
1463
  StatSection,
1438
1464
  TriggerGaugeHost,
1465
+ UpdateDialog,
1439
1466
  getLabeledDropdownContentHeight,
1440
1467
  getOverlayCardColors,
1441
1468
  loadSkiaWeb,