@tastic/profile 0.15.0 → 0.16.1

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
@@ -49,7 +49,9 @@ see its own doc comment for why that component is generic over your own `Profile
49
49
  - **`ProfilePicker`**: a name trigger + selection popover: pick an existing profile, fall back to a
50
50
  null/"guest" selection, or (if `onManage` is passed) navigate to a management screen. Selection
51
51
  only - creating, renaming, and deleting all live in `ProfilesManager`. Generic over your own
52
- `Profile` type (see above). Placement props (all optional, see "Fake-rotated frames" below):
52
+ `Profile` type (see above). `takenId` disables one other seat's profile in the list; `takenIds`
53
+ (an array) disables several, for a lobby with more than two seats, and never disables the
54
+ picker's own selection, so you can pass every seated id. Placement props (all optional, see "Fake-rotated frames" below):
53
55
  `align` (force a horizontal side), `alignOverride` (substitute the whole placement result), and
54
56
  `rotation` (forwarded to the automatic measurement). `colorSection`
55
57
  (`{ value, onChange, previewValue?, swatches?, label?, caption? }`) puts a Color row at the top of
@@ -72,7 +74,7 @@ see its own doc comment for why that component is generic over your own `Profile
72
74
  `profiles`/`defaultColor`/`onCreate`/`onSave`/`onDelete` as `ProfilesManager` itself, plus
73
75
  `onBack`. Owns its own `ProfilesManagerHandle` ref internally - there's no `ref` prop here, since
74
76
  nothing outside it needs one anymore.
75
- - **`readSharedProfiles` / `loadSharedProfiles` / `saveSharedProfiles` / `isSharedProfileStoreAvailable`**: an *optional*
77
+ - **`readSharedProfiles` / `loadSharedProfiles` / `saveSharedProfiles` / `subscribeSharedProfiles` / `isSharedProfileStoreAvailable`**: an *optional*
76
78
  cross-app roster, shared between multiple apps of yours via a native iOS App Group (same Apple
77
79
  Developer Team, same `"com.apple.security.application-groups"` entitlement value on every
78
80
  participating app). This only ever carries the base `Profile` shape above - a host app's own
@@ -103,9 +105,17 @@ see its own doc comment for why that component is generic over your own `Profile
103
105
  result can't replace a roster or lead to a write.
104
106
 
105
107
  Backed by a small native Expo Module (`ios/TasticProfileModule.swift`) wrapping
106
- `UserDefaults(suiteName:)` - iOS only (see `expo-module.config.json`'s `"apple"`-only platform
107
- list); `isSharedProfileStoreAvailable` is `false` on Android/web and on any iOS build that hasn't
108
- run `expo prebuild` since adding this App Group's entitlement, so a host app always needs its own
108
+ `UserDefaults(suiteName:)` on iOS (see `expo-module.config.json`'s `"apple"`-only platform list),
109
+ and on the web by `localStorage`, under the key `<groupId>:sharedProfiles`: games served from one
110
+ origin (for example `games.example.com/<game>/`) share it the way apps share an App Group, while
111
+ games on different origins can't see each other's. The web store writes and removes a probe key
112
+ once at load and stays unavailable if that fails (storage blocked), and after a write that fails
113
+ anyway, reads fail until a write succeeds, so an older roster can't undo a local change.
114
+ `subscribeSharedProfiles(groupId, listener)` hands `listener` the roster another tab or game on
115
+ the same origin has just saved (the browser's `storage` event); it does nothing on native.
116
+ `useSharedProfilesSync` subscribes for you. `isSharedProfileStoreAvailable` is `false` on
117
+ Android, on a web page that can't write `localStorage`, and on any iOS build that hasn't run
118
+ `expo prebuild` since adding this App Group's entitlement, so a host app always needs its own
109
119
  local-storage fallback path regardless of platform. Requires the App Group itself to be declared
110
120
  in each consuming app's own Expo config, e.g.:
111
121
 
@@ -278,7 +288,7 @@ so a host app plugs the reducer(s) into whatever store it already has:
278
288
  Always local, like the guest/CPU/override colors below, and never part of the shared
279
289
  App Group roster, since who's selected on *this* device is naturally per-app, per-device state.
280
290
 
281
- ## Muted profile colors (`ProfileColorProvider`)
291
+ ## Muted profile colors and drawn swatches (`ProfileColorProvider`)
282
292
 
283
293
  An app that draws profile colors through a transform (Solitaire blends them toward its muted
284
294
  card-back palette) wraps its tree once:
@@ -298,6 +308,26 @@ for free. Only what's drawn changes: saved colors, `onCreate`/`onSave` values an
298
308
  untouched. `useProfileColorPreview()` returns the provider's function (or `undefined`), and
299
309
  `useProfileDisplayColor(hex)` resolves one color. Without a provider nothing changes.
300
310
 
311
+ `renderSwatch` (also optional, with or without `preview`) draws the fill of every round swatch this
312
+ package draws, in place of the flat color: `ProfileChip`, the pickers' chip trigger, the Color row's
313
+ swatch and the color page's swatches. It's called as `renderSwatch(color, size)` with the drawn
314
+ color (after `preview`) and the circle's diameter, and its drawing is clipped to the circle, behind
315
+ the tag, icon or check. The tag and check still take their contrast from `color`, so treat it as
316
+ the primary color of whatever you draw. Hangman draws a pie of each color's triad:
317
+
318
+ ```tsx
319
+ import { getTriadicPalette } from '@rific/auto-paper'
320
+ import { ProfileColorProvider, type ProfileSwatchRenderer } from '@tastic/profile'
321
+
322
+ const renderSwatch: ProfileSwatchRenderer = (color, size) => <TriadPie colors={getTriadicPalette(color)} size={size} />
323
+ <ProfileColorProvider renderSwatch={renderSwatch}>{children}</ProfileColorProvider>
324
+ ```
325
+
326
+ (`TriadPie` is your own component, e.g. three `react-native-svg` arcs.) Give it a stable identity
327
+ (module level, or `useCallback`). Rows that fill with a color (a selected picker row) stay flat, and
328
+ so do `@tastic/hud`'s `InlineColorPicker` swatches in `ProfilesManager`'s edit row, which don't read
329
+ this provider. `useProfileSwatchRenderer()` returns it (or `undefined`).
330
+
301
331
  ## Per-seat color persistence
302
332
 
303
333
  A loadout/lobby screen's color for a given seat is never just "the selected profile's saved
@@ -362,11 +392,16 @@ Published to the public npm registry via tag-based CI (OIDC trusted publishing -
362
392
  ## Peer dependencies
363
393
 
364
394
  `react`, `react-native`, `react-native-paper`, `react-native-safe-area-context`, `@rific/auto-paper`
365
- (`getContrastColor`, `useAutoPaperTheme`), `@rific/feedback-press` (`TouchableRipple`),
366
- `@rific/focus-chain` (`useFocusChain`), `@rific/toaster` (`useToast`), `@tastic/hud`
367
- (`InlineColorPicker`, `PopoverBody`, `PopoverHost`, `useAutoAlign`, `usePopoverHost`), `@tastic/core`
368
- (a peer of `@tastic/hud` itself) - none of these are bundled, so use whatever versions your app
369
- already has.
395
+ (`defaultColors`, `FadingScrollView`, `getContrastColor`, `useAutoPaperTheme`), `@rific/feedback-press`
396
+ (`TouchableRipple`), `@rific/focus-chain` (`useFocusChain`), `@rific/toaster` (`useToast`),
397
+ `@tastic/hud` >= 0.23.0 (`ConfirmDialog`, `InlineColorPicker`, `PopoverBody`, `useAutoAlign`,
398
+ `usePopoverHost`), `@tastic/core` (a peer of `@tastic/hud` itself) - none of these are bundled, so
399
+ use whatever versions your app already has.
400
+
401
+ This package imports `@tastic/hud` only through its light `@tastic/hud/chrome` entry, never the main
402
+ one (which also loads Skia and `@tastic/split-screen`), so an app with neither, like Hangman, can use
403
+ every component here. That's why it needs hud 0.23.0, the first whose `./chrome` has the popover
404
+ pieces and the color picker.
370
405
 
371
406
  `expo-modules-core` is an *optional* peer - only resolved by `loadSharedProfiles`/`saveSharedProfiles`
372
407
  for the native App Group bridge described above; safe to omit entirely on an Android/web-only
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { RefObject, ReactNode, Ref } from 'react';
3
- import { PopoverHost, PopoverRotation } from '@tastic/hud';
3
+ import { PopoverHost, PopoverRotation } from '@tastic/hud/chrome';
4
4
  import { View, StyleProp, ViewStyle } from 'react-native';
5
5
  import { SeedColor } from '@rific/auto-paper';
6
6
 
@@ -30,6 +30,7 @@ interface OptionPickerOption<T extends string> {
30
30
  value: T;
31
31
  label: string;
32
32
  icon?: string;
33
+ color?: string;
33
34
  }
34
35
  declare function getOptionPickerContentSize(optionCount: number, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
35
36
  interface OptionPickerProps<T extends string> {
@@ -66,12 +67,15 @@ interface Props {
66
67
  }
67
68
  declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
68
69
 
70
+ type ProfileSwatchRenderer = (color: string, size: number) => ReactNode;
69
71
  interface ProfileColorProviderProps {
70
- preview: (hex: string) => string;
72
+ preview?: (hex: string) => string;
73
+ renderSwatch?: ProfileSwatchRenderer;
71
74
  children: ReactNode;
72
75
  }
73
- declare function ProfileColorProvider({ preview, children }: ProfileColorProviderProps): react.JSX.Element;
76
+ declare function ProfileColorProvider({ preview, renderSwatch, children }: ProfileColorProviderProps): react.JSX.Element;
74
77
  declare function useProfileColorPreview(): ((hex: string) => string) | undefined;
78
+ declare function useProfileSwatchRenderer(): ProfileSwatchRenderer | undefined;
75
79
  declare function useProfileDisplayColor(hex: string): string;
76
80
 
77
81
  declare function getProfilePickerContentSize(profileCount: number, hasManageRow?: boolean, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
@@ -81,6 +85,7 @@ interface ProfilePickerProps<P extends Profile> {
81
85
  profiles: P[];
82
86
  selectedId: string | null;
83
87
  takenId?: string | null;
88
+ takenIds?: readonly string[];
84
89
  color: string;
85
90
  dark: boolean;
86
91
  align?: 'left' | 'right' | 'center';
@@ -94,7 +99,7 @@ interface ProfilePickerProps<P extends Profile> {
94
99
  colorSection?: ProfilePickerColorSection;
95
100
  triggerStyle?: StyleProp<ViewStyle>;
96
101
  }
97
- declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage, colorSection, triggerStyle }: ProfilePickerProps<P>): react.JSX.Element;
102
+ declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, takenIds, color, dark, align, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage, colorSection, triggerStyle }: ProfilePickerProps<P>): react.JSX.Element;
98
103
 
99
104
  type MD3TextVariant$1 = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
100
105
  interface ProfileEditPatch {
@@ -259,5 +264,6 @@ declare const isSharedProfileStoreAvailable: boolean;
259
264
  declare function readSharedProfiles(groupId: string): Promise<Profile[] | null>;
260
265
  declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
261
266
  declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
267
+ declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
262
268
 
263
- export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useSharedProfilesSync };
269
+ export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { RefObject, ReactNode, Ref } from 'react';
3
- import { PopoverHost, PopoverRotation } from '@tastic/hud';
3
+ import { PopoverHost, PopoverRotation } from '@tastic/hud/chrome';
4
4
  import { View, StyleProp, ViewStyle } from 'react-native';
5
5
  import { SeedColor } from '@rific/auto-paper';
6
6
 
@@ -30,6 +30,7 @@ interface OptionPickerOption<T extends string> {
30
30
  value: T;
31
31
  label: string;
32
32
  icon?: string;
33
+ color?: string;
33
34
  }
34
35
  declare function getOptionPickerContentSize(optionCount: number, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
35
36
  interface OptionPickerProps<T extends string> {
@@ -66,12 +67,15 @@ interface Props {
66
67
  }
67
68
  declare function ProfileChip({ profile, size, filled }: Props): react.JSX.Element;
68
69
 
70
+ type ProfileSwatchRenderer = (color: string, size: number) => ReactNode;
69
71
  interface ProfileColorProviderProps {
70
- preview: (hex: string) => string;
72
+ preview?: (hex: string) => string;
73
+ renderSwatch?: ProfileSwatchRenderer;
71
74
  children: ReactNode;
72
75
  }
73
- declare function ProfileColorProvider({ preview, children }: ProfileColorProviderProps): react.JSX.Element;
76
+ declare function ProfileColorProvider({ preview, renderSwatch, children }: ProfileColorProviderProps): react.JSX.Element;
74
77
  declare function useProfileColorPreview(): ((hex: string) => string) | undefined;
78
+ declare function useProfileSwatchRenderer(): ProfileSwatchRenderer | undefined;
75
79
  declare function useProfileDisplayColor(hex: string): string;
76
80
 
77
81
  declare function getProfilePickerContentSize(profileCount: number, hasManageRow?: boolean, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
@@ -81,6 +85,7 @@ interface ProfilePickerProps<P extends Profile> {
81
85
  profiles: P[];
82
86
  selectedId: string | null;
83
87
  takenId?: string | null;
88
+ takenIds?: readonly string[];
84
89
  color: string;
85
90
  dark: boolean;
86
91
  align?: 'left' | 'right' | 'center';
@@ -94,7 +99,7 @@ interface ProfilePickerProps<P extends Profile> {
94
99
  colorSection?: ProfilePickerColorSection;
95
100
  triggerStyle?: StyleProp<ViewStyle>;
96
101
  }
97
- declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage, colorSection, triggerStyle }: ProfilePickerProps<P>): react.JSX.Element;
102
+ declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, takenIds, color, dark, align, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage, colorSection, triggerStyle }: ProfilePickerProps<P>): react.JSX.Element;
98
103
 
99
104
  type MD3TextVariant$1 = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
100
105
  interface ProfileEditPatch {
@@ -259,5 +264,6 @@ declare const isSharedProfileStoreAvailable: boolean;
259
264
  declare function readSharedProfiles(groupId: string): Promise<Profile[] | null>;
260
265
  declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
261
266
  declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
267
+ declare function subscribeSharedProfiles(groupId: string, listener: (profiles: Profile[]) => void): () => void;
262
268
 
263
- export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useSharedProfilesSync };
269
+ export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, OptionPicker, type OptionPickerOption, type Profile, ProfileChip, type ProfileColorPick, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, type ProfileSwatchRenderer, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileColorPayload, type SetProfileExtensionPayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getOptionPickerContentSize, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profileSeatColor, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, subscribeSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useProfileSwatchRenderer, useSharedProfilesSync };