@tastic/hud 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -209,12 +209,16 @@ its own `useGameSettings()` hook internally instead of taking it as props. Snake
209
209
  the fleet with real Redux Toolkit state - reads `lockOrientation`/`deferBottomEdgeGestures` via
210
210
  `useSelector` and writes them back via `dispatch(gameActions.setLockOrientation(...))` directly
211
211
  inside the adapter, with nothing threaded through props at all. `BaseSettingsDialog` doesn't care
212
- which of these an app uses: sound, haptics, appearance, and update-checking are identical across
213
- every app and live entirely inside this package now, so `lockOrientation`/`onLockOrientationChange`,
214
- `deferBottomEdgeGestures`/`onDeferBottomEdgeGestures` and `keepAwake`/`onKeepAwakeChange` are the only
215
- settings an adapter actually needs to source itself, however it does that. Each pair is optional: omit
216
- one and its row doesn't show. The Keep Awake row only toggles the value; the app keeps the screen awake
217
- itself while it's on (for example with `expo-keep-awake` on its match screen).
212
+ which of these an app uses: sound, haptics, appearance, update-checking and Keep Awake are identical
213
+ across every app and read their own shared settings, so `lockOrientation`/`onLockOrientationChange`
214
+ and `deferBottomEdgeGestures`/`onDeferBottomEdgeGestures` are the only settings an adapter actually
215
+ needs to source itself, however it does that. Each pair is optional: omit one and its row doesn't show.
216
+
217
+ The Keep Awake row reads `@rific/core`'s Keep Awake setting (`useKeepAwakeSettings()`), the same way
218
+ the Sound row reads `@rific/feedback-press`. It shows on touch-first devices (where Lock Orientation
219
+ shows) in any app whose `KeepAwakeProvider` was given `expo-keep-awake` (`useExpoKeepAwake()` returns
220
+ it), and is hidden otherwise. The app opts in once at its root and calls `useKeepAwake('game')` on the
221
+ screen that should stay awake; see `@rific/core`'s README for the bridge.
218
222
 
219
223
  Every adapter also passes `version={release.otaVersion}` (so the dialog's own update-check button
220
224
  knows what it's currently running) and `onUpdateError`, wired to a real `useToast()` call. Wire this
@@ -239,8 +243,6 @@ export function SettingsDialog({ visible, onDismiss, rotation = 0, settings, set
239
243
  onLockOrientationChange={(value) => setSettings({ lockOrientation: value })}
240
244
  deferBottomEdgeGestures={settings.deferBottomEdgeGestures}
241
245
  onDeferBottomEdgeGestures={(value) => setSettings({ deferBottomEdgeGestures: value })}
242
- keepAwake={settings.keepAwake}
243
- onKeepAwakeChange={(value) => setSettings({ keepAwake: value })}
244
246
  onUpdateError={(message) => error('Update check failed', message)}
245
247
  />
246
248
  )
@@ -628,17 +630,26 @@ const { open } = useGuide()
628
630
 
629
631
  ## Fleet chrome without the heavy peers (`@tastic/hud/chrome`)
630
632
 
631
- The shared dialogs, stats screen pieces, corner buttons and style tokens, from a standalone entry that never
632
- touches the main barrel. It's for an app that has `@tastic/core` and `@rific/updater` but none of Skia,
633
- `@tastic/profile` or `@tastic/split-screen` (Hangman). It exports, with their props types:
633
+ The shared dialogs, stats screen pieces, corner buttons, popover primitives, color picker and style tokens,
634
+ from a standalone entry that never touches the main barrel. It's for an app that has `@tastic/core` and
635
+ `@rific/updater` but none of Skia, `@tastic/profile` or `@tastic/split-screen` (Hangman). It exports, with
636
+ their props types:
634
637
 
635
638
  - Dialogs: `BaseSettingsDialog`, `ConfirmDialog`, `UpdateDialog`, `RotatedFrame`
636
639
  - Stats: `BaseStatsScreen`, `StatSection`, `StatRow`, `AchievementRow`, `LOCKED_BADGE_COLOR`,
637
640
  `AchievementCatalogSection`, `ActivityStatSection`, `useWellColors`, `WELL_SHADOW`
638
641
  - Chrome: `CornerActionButtons`, `ContentGutter`, `StaggeredWord`, `getStaggeredWordDuration`
639
642
  - Overlay cards: `OverlayCard`, `OverlayBackdrop`, `useOverlayCardColors`, `DialogHeader`, `useCompactDialog`, `isCompactDialogHeight`, `COMPACT_DIALOG_HEIGHT`
643
+ - Popovers (0.23.0+): `PopoverBody`, `PressAwayOverlay`, `usePopoverHost` (and its `PopoverHost` type),
644
+ `useAutoAlign` (and its `AlignResult`, `PopoverAlign`, `PopoverRotation` and `PopoverVerticalAlign`
645
+ types), `InlineColorPicker`, `getInlineColorPickerContentSize`
640
646
  - Style tokens: `overlayStyles`, `overlayActionStyles`, `MONO_FONT`
641
647
 
648
+ The popovers are here for `@tastic/profile`: from 0.16 its `ProfilePicker`, `ProfilesScreen` and the rest
649
+ import them (and `ConfirmDialog`) from this entry instead of the main barrel, so a Skia-free app can use
650
+ them. That's why `@tastic/profile` 0.16 needs hud 0.23.0 or later. `useZoneClampedAlign` (it needs `@tastic/split-screen`), `SectionedDropdown` (its trigger gauge
651
+ loads Skia) and `PlayerSetupPanel` (it renders `@tastic/profile`'s pickers) stay main-entry only.
652
+
642
653
  Each is the same component the main entry exports and is documented above. Only the import path changes:
643
654
 
644
655
  ```tsx
@@ -646,8 +657,8 @@ import { BaseSettingsDialog, ConfirmDialog, CornerActionButtons } from '@tastic/
646
657
  ```
647
658
 
648
659
  It pulls in only `react`, `react-native`, `react-native-paper`, `react-native-reanimated`,
649
- `react-native-safe-area-context`, `@rific/auto-paper`, `@rific/feedback-press`, `@rific/updater` and
650
- `@tastic/core`. `AchievementCatalogSection` uses `@tastic/achievements` for types only, so nothing loads it at
660
+ `react-native-safe-area-context`, `@rific/auto-paper`, `@rific/core`, `@rific/feedback-press`,
661
+ `@rific/updater` and `@tastic/core`. `AchievementCatalogSection` uses `@tastic/achievements` for types only, so nothing loads it at
651
662
  runtime, but an app whose TypeScript resolves the `react-native` export condition (the fleet's tsconfig does)
652
663
  type-checks hud's source and needs it installed. `AchievementUnlockList` isn't here because it renders
653
664
  `@tastic/profile`'s `ProfileChip`. A test (`src/__tests__/chromeImports.test.ts`) walks the import graph from
@@ -657,6 +668,64 @@ type-checks hud's source and needs it installed. `AchievementUnlockList` isn't h
657
668
  Metro resolves both entries to the same source files, so an app that imports some components from here and
658
669
  others from `@tastic/hud` still bundles each module once.
659
670
 
671
+ ## Skia on web (`@tastic/hud/skia`)
672
+
673
+ On web, Skia draws through CanvasKit, a WASM file fetched at runtime. `@shopify/react-native-skia` builds
674
+ its `Skia` object from CanvasKit when the module is first evaluated, so a component that imports Skia can't
675
+ even be imported until CanvasKit has loaded. On native there is nothing to wait for.
676
+
677
+ Every app in the fleet loads Skia the same way, with a host pair: two small files per Skia component, side
678
+ by side, that Metro picks between by platform.
679
+
680
+ ```tsx
681
+ // BoardHost.tsx: native renders the board directly, on the first frame
682
+ import { Board, type BoardProps } from './Board'
683
+
684
+ export default function BoardHost(props: BoardProps) {
685
+ return <Board {...props} />
686
+ }
687
+ ```
688
+
689
+ ```tsx
690
+ // BoardHost.web.tsx: web imports the board only once CanvasKit has loaded
691
+ import { loadSkiaWeb } from '@tastic/hud/skia'
692
+ import { lazy, Suspense } from 'react'
693
+
694
+ import type { BoardProps } from './Board'
695
+
696
+ const LazyBoard = lazy(() => loadSkiaWeb().then(() => import('./Board').then((m) => ({ default: m.Board }))))
697
+
698
+ export default function BoardHost(props: BoardProps) {
699
+ return (
700
+ <Suspense fallback={null}>
701
+ <LazyBoard {...props} />
702
+ </Suspense>
703
+ )
704
+ }
705
+ ```
706
+
707
+ - Import the host, never `Board` itself, everywhere else in the app.
708
+ - Keep `lazy()` at module scope, as above, so the component is created once.
709
+ - Import from `@tastic/hud/skia` (also exported from `@tastic/hud`). It carries none of the main entry's
710
+ heavier peers, so it works in every app.
711
+ - A test that imports a native host imports Skia, which throws under Jest (no native bindings). Mock the
712
+ host, or don't import the screen that uses it.
713
+
714
+ `loadSkiaWeb()` fetches the app's `public/canvaskit.wasm` from `<baseUrl>/canvaskit.wasm`: under the Expo base
715
+ URL (`experiments.baseUrl`, e.g. `/snake/canvaskit.wasm`) when the app has one, and `/canvaskit.wasm` when it
716
+ doesn't. There's nothing to wire up: babel-preset-expo inlines `process.env.EXPO_BASE_URL` into hud's source
717
+ when Metro bundles it, the same as in the app's own code. It's idempotent, so every host can call it.
718
+
719
+ That rule is `locateSkiaWebFile(file: string): string`, also exported from `@tastic/hud/skia`, for code that
720
+ calls Skia's own `LoadSkiaWeb` or `WithSkiaWeb` (`opts={{ locateFile: locateSkiaWebFile }}`). Without it,
721
+ canvaskit looks for the file next to the running script or the page, which under a base URL is the wrong
722
+ place: from `/snake` (no trailing slash) it asks for `/canvaskit.wasm`, and a static-output app can end up
723
+ asking under `/_expo/static/js/web/`. `LoadSkiaWeb` also keeps its first call's result, a failed load
724
+ included, for the rest of the page's life.
725
+
726
+ `TriggerGaugeHost` uses the web half on both platforms: the main entry can't import Skia up front, since every
727
+ app that imports `@tastic/hud` would then load Skia, including in its tests.
728
+
660
729
  ## Install
661
730
 
662
731
  Published to the public npm registry as `@tastic/hud`. Everything described above, including the
@@ -693,10 +762,11 @@ resolve module".
693
762
  | `react-native-reanimated` | >=3.0.0 | main entry (`StaggeredWord`), `./guide`, `./chrome` (`StaggeredWord`) |
694
763
  | `@rific/auto-paper` | >=0.12.2 | main entry (`BaseSettingsDialog` needs the nested `Portal.Host` first shipped in 0.12.2; `OverlayCard` uses `BlurView` and `useBlur`, and `BaseSettingsDialog`'s Blur row uses `useBlurModule` and `useThemeSettings`, all in 0.12.2), `./guide`, `./chrome` |
695
764
  | `@rific/feedback-press` | >=0.10.2 | main entry (`useSoundSettings` first shipped in 0.10.2), `./guide`, `./chrome` |
765
+ | `@rific/core` | >=0.4.0 | main entry and `./chrome`: `BaseSettingsDialog`'s Keep Awake row (`useKeepAwakeSettings`, `useExpoKeepAwake`, first shipped in 0.4.0) |
696
766
  | `react-native-safe-area-context` | >=5.0.0 | main entry (`useZoneClampedAlign`), `./chrome` (`RotatedFrame`, `BaseSettingsDialog`) |
697
- | `@tastic/core` | >=0.12.0, optional | main entry: `useRotation()` and most dialogs; `RotatedFrame` and `BaseSettingsDialog` need `FakeLandscapeView`'s `rotation`/`passThrough`, first shipped in 0.12.0; `./chrome` (the same dialogs) |
767
+ | `@tastic/core` | >=0.12.0, optional | main entry: `useRotation()` and most dialogs; `RotatedFrame` and `BaseSettingsDialog` need `FakeLandscapeView`'s `rotation`/`passThrough`, first shipped in 0.12.0; `./chrome` (the same dialogs, and `InlineColorPicker`) |
698
768
  | `@rific/updater` | >=0.4.0, optional | main entry and `./chrome`: `BaseSettingsDialog`, `UpdateDialog` |
699
769
  | `@tastic/profile` | >=0.4.0, optional | main entry: `PlayerSetupPanel`, `AchievementUnlockList` (`ProfileChip`) |
700
770
  | `@tastic/split-screen` | >=0.5.0, optional | main entry: `useZoneClampedAlign` |
701
- | `@shopify/react-native-skia` | >=2.6.0, optional | `TriggerGaugeHost`'s gauge (lazy-loaded behind `SkiaGate`) and `./skia-gate` |
771
+ | `@shopify/react-native-skia` | >=2.6.0, optional | `TriggerGaugeHost`'s gauge (loaded when one mounts) and `./skia` |
702
772
  | `@tastic/achievements` | >=0.2.0, optional | `AchievementCatalogSection`, `AchievementUnlockList`, type-only (`AchievementCatalogRow`, first exported in 0.2.0; `AchievementTier`); `./chrome` needs it installed only to type-check |
@@ -1,20 +1,21 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
3
  import { AchievementCatalogRow } from '@tastic/achievements';
4
4
  import { ViewRotation } from '@tastic/core';
5
- import { Insets, StyleProp, ViewStyle, LayoutChangeEvent, TextStyle } from 'react-native';
5
+ import { Insets, StyleProp, ViewStyle, View, LayoutChangeEvent, TextStyle } from 'react-native';
6
+ import { SeedColor } from '@rific/auto-paper';
6
7
 
7
- interface Props$7 {
8
+ interface Props$a {
8
9
  rows: AchievementCatalogRow[];
9
10
  label?: string;
10
11
  fg?: string;
11
12
  fgMuted?: string;
12
13
  sectionBg?: string;
13
14
  }
14
- declare function AchievementCatalogSection({ rows, label, fg, fgMuted: fgMutedOverride, sectionBg }: Props$7): react.JSX.Element;
15
+ declare function AchievementCatalogSection({ rows, label, fg, fgMuted: fgMutedOverride, sectionBg }: Props$a): react.JSX.Element;
15
16
 
16
17
  declare const LOCKED_BADGE_COLOR = "rgba(128,128,128,0.3)";
17
- interface Props$6 {
18
+ interface Props$9 {
18
19
  icon: string;
19
20
  title: string;
20
21
  description: string;
@@ -27,20 +28,20 @@ interface Props$6 {
27
28
  fgMuted?: string;
28
29
  sectionBg?: string;
29
30
  }
30
- declare function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }: Props$6): react.JSX.Element;
31
+ declare function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }: Props$9): react.JSX.Element;
31
32
 
32
33
  interface ActivityStats {
33
34
  distinctDaysPlayed: number;
34
35
  currentDayStreak: number;
35
36
  bestDayStreak: number;
36
37
  }
37
- interface Props$5 {
38
+ interface Props$8 {
38
39
  stats: ActivityStats;
39
40
  fg?: string;
40
41
  fgMuted?: string;
41
42
  sectionBg?: string;
42
43
  }
43
- declare function ActivityStatSection({ stats, fg, fgMuted, sectionBg }: Props$5): react.JSX.Element;
44
+ declare function ActivityStatSection({ stats, fg, fgMuted, sectionBg }: Props$8): react.JSX.Element;
44
45
 
45
46
  interface BaseSettingsDialogProps {
46
47
  visible: boolean;
@@ -51,8 +52,6 @@ interface BaseSettingsDialogProps {
51
52
  onLockOrientationChange?: (value: boolean) => void;
52
53
  deferBottomEdgeGestures?: boolean;
53
54
  onDeferBottomEdgeGestures?: (value: boolean) => void;
54
- keepAwake?: boolean;
55
- onKeepAwakeChange?: (value: boolean) => void;
56
55
  hideSound?: boolean;
57
56
  hideHaptics?: boolean;
58
57
  hideAppearance?: boolean;
@@ -61,10 +60,10 @@ interface BaseSettingsDialogProps {
61
60
  onShowHowToPlay?: () => void;
62
61
  children?: ReactNode;
63
62
  }
64
- declare function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, keepAwake, onKeepAwakeChange, hideSound, hideHaptics, hideAppearance, hideUpdateCheck, onUpdateError, onShowHowToPlay, children }: BaseSettingsDialogProps): react.JSX.Element | null;
63
+ declare function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound, hideHaptics, hideAppearance, hideUpdateCheck, onUpdateError, onShowHowToPlay, children }: BaseSettingsDialogProps): react.JSX.Element | null;
65
64
 
66
65
  type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
67
- interface Props$4 {
66
+ interface Props$7 {
68
67
  rotation?: ViewRotation;
69
68
  title?: string;
70
69
  titleVariant?: MD3TextVariant;
@@ -85,7 +84,7 @@ interface Props$4 {
85
84
  bg?: string;
86
85
  children: ReactNode;
87
86
  }
88
- declare function BaseStatsScreen({ rotation: rotationOverride, title, titleVariant, onBack, backLabel, hitSlop, insets, onReset, resetLabel, resetConfirmTitle, resetConfirmBody, fg: fgOverride, bg: bgOverride, children }: Props$4): react.JSX.Element;
87
+ declare function BaseStatsScreen({ rotation: rotationOverride, title, titleVariant, onBack, backLabel, hitSlop, insets, onReset, resetLabel, resetConfirmTitle, resetConfirmBody, fg: fgOverride, bg: bgOverride, children }: Props$7): react.JSX.Element;
89
88
 
90
89
  interface ConfirmDialogProps {
91
90
  visible: boolean;
@@ -101,16 +100,16 @@ interface ConfirmDialogProps {
101
100
  }
102
101
  declare function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation, icon, destructive }: ConfirmDialogProps): react.JSX.Element | null;
103
102
 
104
- interface Props$3 {
103
+ interface Props$6 {
105
104
  maxContentWidth?: number;
106
105
  leftGutter?: ReactNode;
107
106
  rightGutter?: ReactNode;
108
107
  style?: StyleProp<ViewStyle>;
109
108
  children: ReactNode;
110
109
  }
111
- declare function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }: Props$3): react.JSX.Element;
110
+ declare function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }: Props$6): react.JSX.Element;
112
111
 
113
- interface Props$2 {
112
+ interface Props$5 {
114
113
  onBack: () => void;
115
114
  onSettings: () => void;
116
115
  fg: string;
@@ -123,7 +122,7 @@ interface Props$2 {
123
122
  backLabel?: string;
124
123
  hitSlop?: number | Insets;
125
124
  }
126
- declare function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon, backLabel, hitSlop }: Props$2): react.JSX.Element;
125
+ declare function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon, backLabel, hitSlop }: Props$5): react.JSX.Element;
127
126
 
128
127
  declare const COMPACT_DIALOG_HEIGHT = 528;
129
128
  declare function isCompactDialogHeight(height: number): boolean;
@@ -138,6 +137,57 @@ declare function DialogHeader({ icon, title, color, compact, style }: DialogHead
138
137
 
139
138
  declare const MONO_FONT: string;
140
139
 
140
+ type PopoverAlign = 'left' | 'right' | 'center';
141
+ type PopoverVerticalAlign = 'below' | 'above';
142
+ type PopoverRotation = ViewRotation;
143
+ interface AlignResult {
144
+ align: PopoverAlign;
145
+ verticalAlign: PopoverVerticalAlign;
146
+ maxHeight: number;
147
+ measured: boolean;
148
+ triggerRef: RefObject<View | null>;
149
+ }
150
+ declare function useAutoAlign(open: boolean, contentWidth: number, contentHeight: number, rotation?: PopoverRotation): {
151
+ align: PopoverAlign;
152
+ maxHeight: number;
153
+ measured: boolean;
154
+ triggerRef: RefObject<View | null>;
155
+ verticalAlign: PopoverVerticalAlign;
156
+ };
157
+
158
+ interface PopoverHost {
159
+ openId: string | null;
160
+ toggle: (id: string) => void;
161
+ close: () => void;
162
+ }
163
+ declare function usePopoverHost(): PopoverHost;
164
+
165
+ declare function getInlineColorPickerContentSize(swatchCount: number, windowWidth: number, columns?: number): {
166
+ width: number;
167
+ height: number;
168
+ };
169
+ interface Props$4 {
170
+ id: string;
171
+ host: PopoverHost;
172
+ value: string;
173
+ onChange: (hex: string) => void;
174
+ previewValue?: (hex: string) => string;
175
+ swatches?: SeedColor[];
176
+ takenValue?: string;
177
+ allowSwapTaken?: boolean;
178
+ dark: boolean;
179
+ align?: 'left' | 'right' | 'center';
180
+ alignOverride?: AlignResult;
181
+ rotation?: PopoverRotation;
182
+ icon?: string;
183
+ tag?: string;
184
+ labelFontFamily?: string;
185
+ autoDismiss?: boolean;
186
+ columns?: number;
187
+ size?: number;
188
+ }
189
+ declare function InlineColorPicker({ id, host, value, onChange, previewValue, swatches, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon, tag, labelFontFamily, autoDismiss, columns, size }: Props$4): react.JSX.Element;
190
+
141
191
  declare const overlayStyles: {
142
192
  card: {
143
193
  alignItems: "center";
@@ -195,6 +245,25 @@ interface OverlayCardProps {
195
245
  }
196
246
  declare function OverlayCard({ children, style, ...rest }: OverlayCardProps): react.JSX.Element;
197
247
 
248
+ interface Props$3 {
249
+ visible: boolean;
250
+ children: ReactNode;
251
+ align?: 'left' | 'right' | 'center';
252
+ verticalAlign?: 'below' | 'above';
253
+ caretColor?: string;
254
+ caretBorderColor?: string;
255
+ caretBorderWidth?: number;
256
+ triggerSize?: number;
257
+ }
258
+ declare function PopoverBody({ visible, children, align, verticalAlign, caretColor, caretBorderColor, caretBorderWidth, triggerSize }: Props$3): react.JSX.Element | null;
259
+
260
+ interface Props$2 {
261
+ active: boolean;
262
+ onPress: () => void;
263
+ style?: StyleProp<ViewStyle>;
264
+ }
265
+ declare function PressAwayOverlay({ active, onPress, style }: Props$2): react.JSX.Element | null;
266
+
198
267
  interface RotatedFrameProps {
199
268
  children: ReactNode;
200
269
  /** The angle to turn the frame by. Defaults to the live physical hold (@tastic/core's useRotation); pass it
@@ -282,4 +351,4 @@ interface WellColors {
282
351
  }
283
352
  declare function useWellColors(): WellColors;
284
353
 
285
- export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, type DialogHeaderProps, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, WELL_SHADOW, type WellColors, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useCompactDialog, useOverlayCardColors, useWellColors };
354
+ export { AchievementCatalogSection, AchievementRow, ActivityStatSection, type AlignResult, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, type DialogHeaderProps, InlineColorPicker, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, WELL_SHADOW, type WellColors, getInlineColorPickerContentSize, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useAutoAlign, useCompactDialog, useOverlayCardColors, usePopoverHost, useWellColors };
@@ -1,20 +1,21 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, RefObject } from 'react';
3
3
  import { AchievementCatalogRow } from '@tastic/achievements';
4
4
  import { ViewRotation } from '@tastic/core';
5
- import { Insets, StyleProp, ViewStyle, LayoutChangeEvent, TextStyle } from 'react-native';
5
+ import { Insets, StyleProp, ViewStyle, View, LayoutChangeEvent, TextStyle } from 'react-native';
6
+ import { SeedColor } from '@rific/auto-paper';
6
7
 
7
- interface Props$7 {
8
+ interface Props$a {
8
9
  rows: AchievementCatalogRow[];
9
10
  label?: string;
10
11
  fg?: string;
11
12
  fgMuted?: string;
12
13
  sectionBg?: string;
13
14
  }
14
- declare function AchievementCatalogSection({ rows, label, fg, fgMuted: fgMutedOverride, sectionBg }: Props$7): react.JSX.Element;
15
+ declare function AchievementCatalogSection({ rows, label, fg, fgMuted: fgMutedOverride, sectionBg }: Props$a): react.JSX.Element;
15
16
 
16
17
  declare const LOCKED_BADGE_COLOR = "rgba(128,128,128,0.3)";
17
- interface Props$6 {
18
+ interface Props$9 {
18
19
  icon: string;
19
20
  title: string;
20
21
  description: string;
@@ -27,20 +28,20 @@ interface Props$6 {
27
28
  fgMuted?: string;
28
29
  sectionBg?: string;
29
30
  }
30
- declare function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }: Props$6): react.JSX.Element;
31
+ declare function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }: Props$9): react.JSX.Element;
31
32
 
32
33
  interface ActivityStats {
33
34
  distinctDaysPlayed: number;
34
35
  currentDayStreak: number;
35
36
  bestDayStreak: number;
36
37
  }
37
- interface Props$5 {
38
+ interface Props$8 {
38
39
  stats: ActivityStats;
39
40
  fg?: string;
40
41
  fgMuted?: string;
41
42
  sectionBg?: string;
42
43
  }
43
- declare function ActivityStatSection({ stats, fg, fgMuted, sectionBg }: Props$5): react.JSX.Element;
44
+ declare function ActivityStatSection({ stats, fg, fgMuted, sectionBg }: Props$8): react.JSX.Element;
44
45
 
45
46
  interface BaseSettingsDialogProps {
46
47
  visible: boolean;
@@ -51,8 +52,6 @@ interface BaseSettingsDialogProps {
51
52
  onLockOrientationChange?: (value: boolean) => void;
52
53
  deferBottomEdgeGestures?: boolean;
53
54
  onDeferBottomEdgeGestures?: (value: boolean) => void;
54
- keepAwake?: boolean;
55
- onKeepAwakeChange?: (value: boolean) => void;
56
55
  hideSound?: boolean;
57
56
  hideHaptics?: boolean;
58
57
  hideAppearance?: boolean;
@@ -61,10 +60,10 @@ interface BaseSettingsDialogProps {
61
60
  onShowHowToPlay?: () => void;
62
61
  children?: ReactNode;
63
62
  }
64
- declare function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, keepAwake, onKeepAwakeChange, hideSound, hideHaptics, hideAppearance, hideUpdateCheck, onUpdateError, onShowHowToPlay, children }: BaseSettingsDialogProps): react.JSX.Element | null;
63
+ declare function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound, hideHaptics, hideAppearance, hideUpdateCheck, onUpdateError, onShowHowToPlay, children }: BaseSettingsDialogProps): react.JSX.Element | null;
65
64
 
66
65
  type MD3TextVariant = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
67
- interface Props$4 {
66
+ interface Props$7 {
68
67
  rotation?: ViewRotation;
69
68
  title?: string;
70
69
  titleVariant?: MD3TextVariant;
@@ -85,7 +84,7 @@ interface Props$4 {
85
84
  bg?: string;
86
85
  children: ReactNode;
87
86
  }
88
- declare function BaseStatsScreen({ rotation: rotationOverride, title, titleVariant, onBack, backLabel, hitSlop, insets, onReset, resetLabel, resetConfirmTitle, resetConfirmBody, fg: fgOverride, bg: bgOverride, children }: Props$4): react.JSX.Element;
87
+ declare function BaseStatsScreen({ rotation: rotationOverride, title, titleVariant, onBack, backLabel, hitSlop, insets, onReset, resetLabel, resetConfirmTitle, resetConfirmBody, fg: fgOverride, bg: bgOverride, children }: Props$7): react.JSX.Element;
89
88
 
90
89
  interface ConfirmDialogProps {
91
90
  visible: boolean;
@@ -101,16 +100,16 @@ interface ConfirmDialogProps {
101
100
  }
102
101
  declare function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation, icon, destructive }: ConfirmDialogProps): react.JSX.Element | null;
103
102
 
104
- interface Props$3 {
103
+ interface Props$6 {
105
104
  maxContentWidth?: number;
106
105
  leftGutter?: ReactNode;
107
106
  rightGutter?: ReactNode;
108
107
  style?: StyleProp<ViewStyle>;
109
108
  children: ReactNode;
110
109
  }
111
- declare function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }: Props$3): react.JSX.Element;
110
+ declare function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }: Props$6): react.JSX.Element;
112
111
 
113
- interface Props$2 {
112
+ interface Props$5 {
114
113
  onBack: () => void;
115
114
  onSettings: () => void;
116
115
  fg: string;
@@ -123,7 +122,7 @@ interface Props$2 {
123
122
  backLabel?: string;
124
123
  hitSlop?: number | Insets;
125
124
  }
126
- declare function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon, backLabel, hitSlop }: Props$2): react.JSX.Element;
125
+ declare function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon, backLabel, hitSlop }: Props$5): react.JSX.Element;
127
126
 
128
127
  declare const COMPACT_DIALOG_HEIGHT = 528;
129
128
  declare function isCompactDialogHeight(height: number): boolean;
@@ -138,6 +137,57 @@ declare function DialogHeader({ icon, title, color, compact, style }: DialogHead
138
137
 
139
138
  declare const MONO_FONT: string;
140
139
 
140
+ type PopoverAlign = 'left' | 'right' | 'center';
141
+ type PopoverVerticalAlign = 'below' | 'above';
142
+ type PopoverRotation = ViewRotation;
143
+ interface AlignResult {
144
+ align: PopoverAlign;
145
+ verticalAlign: PopoverVerticalAlign;
146
+ maxHeight: number;
147
+ measured: boolean;
148
+ triggerRef: RefObject<View | null>;
149
+ }
150
+ declare function useAutoAlign(open: boolean, contentWidth: number, contentHeight: number, rotation?: PopoverRotation): {
151
+ align: PopoverAlign;
152
+ maxHeight: number;
153
+ measured: boolean;
154
+ triggerRef: RefObject<View | null>;
155
+ verticalAlign: PopoverVerticalAlign;
156
+ };
157
+
158
+ interface PopoverHost {
159
+ openId: string | null;
160
+ toggle: (id: string) => void;
161
+ close: () => void;
162
+ }
163
+ declare function usePopoverHost(): PopoverHost;
164
+
165
+ declare function getInlineColorPickerContentSize(swatchCount: number, windowWidth: number, columns?: number): {
166
+ width: number;
167
+ height: number;
168
+ };
169
+ interface Props$4 {
170
+ id: string;
171
+ host: PopoverHost;
172
+ value: string;
173
+ onChange: (hex: string) => void;
174
+ previewValue?: (hex: string) => string;
175
+ swatches?: SeedColor[];
176
+ takenValue?: string;
177
+ allowSwapTaken?: boolean;
178
+ dark: boolean;
179
+ align?: 'left' | 'right' | 'center';
180
+ alignOverride?: AlignResult;
181
+ rotation?: PopoverRotation;
182
+ icon?: string;
183
+ tag?: string;
184
+ labelFontFamily?: string;
185
+ autoDismiss?: boolean;
186
+ columns?: number;
187
+ size?: number;
188
+ }
189
+ declare function InlineColorPicker({ id, host, value, onChange, previewValue, swatches, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon, tag, labelFontFamily, autoDismiss, columns, size }: Props$4): react.JSX.Element;
190
+
141
191
  declare const overlayStyles: {
142
192
  card: {
143
193
  alignItems: "center";
@@ -195,6 +245,25 @@ interface OverlayCardProps {
195
245
  }
196
246
  declare function OverlayCard({ children, style, ...rest }: OverlayCardProps): react.JSX.Element;
197
247
 
248
+ interface Props$3 {
249
+ visible: boolean;
250
+ children: ReactNode;
251
+ align?: 'left' | 'right' | 'center';
252
+ verticalAlign?: 'below' | 'above';
253
+ caretColor?: string;
254
+ caretBorderColor?: string;
255
+ caretBorderWidth?: number;
256
+ triggerSize?: number;
257
+ }
258
+ declare function PopoverBody({ visible, children, align, verticalAlign, caretColor, caretBorderColor, caretBorderWidth, triggerSize }: Props$3): react.JSX.Element | null;
259
+
260
+ interface Props$2 {
261
+ active: boolean;
262
+ onPress: () => void;
263
+ style?: StyleProp<ViewStyle>;
264
+ }
265
+ declare function PressAwayOverlay({ active, onPress, style }: Props$2): react.JSX.Element | null;
266
+
198
267
  interface RotatedFrameProps {
199
268
  children: ReactNode;
200
269
  /** The angle to turn the frame by. Defaults to the live physical hold (@tastic/core's useRotation); pass it
@@ -282,4 +351,4 @@ interface WellColors {
282
351
  }
283
352
  declare function useWellColors(): WellColors;
284
353
 
285
- export { AchievementCatalogSection, AchievementRow, ActivityStatSection, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, type DialogHeaderProps, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, WELL_SHADOW, type WellColors, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useCompactDialog, useOverlayCardColors, useWellColors };
354
+ export { AchievementCatalogSection, AchievementRow, ActivityStatSection, type AlignResult, BaseSettingsDialog, type BaseSettingsDialogProps, BaseStatsScreen, COMPACT_DIALOG_HEIGHT, ConfirmDialog, type ConfirmDialogProps, ContentGutter, CornerActionButtons, DialogHeader, type DialogHeaderProps, InlineColorPicker, LOCKED_BADGE_COLOR, MONO_FONT, OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, RotatedFrame, type RotatedFrameProps, StaggeredWord, type StaggeredWordProps, StatRow, StatSection, UpdateDialog, type UpdateDialogProps, WELL_SHADOW, type WellColors, getInlineColorPickerContentSize, getStaggeredWordDuration, isCompactDialogHeight, overlayActionStyles, overlayStyles, useAutoAlign, useCompactDialog, useOverlayCardColors, usePopoverHost, useWellColors };