@tastic/profile 0.10.0 → 0.11.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 +7 -2
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -1
- package/dist/index.mjs +15 -1
- package/package.json +1 -1
- package/src/redux/profileSelectionSlice.ts +21 -1
package/README.md
CHANGED
|
@@ -244,13 +244,18 @@ so a host app plugs the reducer(s) into whatever store it already has:
|
|
|
244
244
|
- **`createProfileSelectionSlice<TSeat>(namespace, defaultState)`** — a factory for the *other*
|
|
245
245
|
per-seat piece of state: which profile id each seat currently has selected (parallel to
|
|
246
246
|
`createProfileExtensionSlice` above, but keyed by seat instead of by profile id). Returns a
|
|
247
|
-
`{ actions: { select, clearProfile }, reducer }` pair over a plain
|
|
247
|
+
`{ actions: { select, clearProfile, pruneToRoster }, reducer }` pair over a plain
|
|
248
|
+
`Record<TSeat, string | null>`.
|
|
248
249
|
Unlike `createProfileExtensionSlice`, the seat set can't be derived from `TSeat` alone (types
|
|
249
250
|
don't exist at runtime), so callers pass their own `defaultState` — the same literal object (e.g.
|
|
250
251
|
`{ 1: null, 2: null }`) every app already declares today. `select({ seat, profileId })` sets one
|
|
251
252
|
seat's selection directly; `clearProfile(profileId)` walks every seat and nulls out any that
|
|
252
253
|
currently points at that id — call it when a profile is deleted so no seat is left referencing a
|
|
253
|
-
ghost id.
|
|
254
|
+
ghost id. `pruneToRoster(ids)` nulls every seat whose id is no longer in the given roster; dispatch
|
|
255
|
+
it after both `profilesActions.setAll` paths (the initial `resolveInitialProfiles` reconcile and
|
|
256
|
+
`onRemoteChange`), since a sibling app can delete a profile without this app's own delete ever
|
|
257
|
+
running, and it returns the same state object when nothing changes.
|
|
258
|
+
Always local, like the guest/CPU/override colors below, and never part of the shared
|
|
254
259
|
App Group roster, since who's selected on *this* device is naturally per-app, per-device state.
|
|
255
260
|
|
|
256
261
|
## Muted profile colors (`ProfileColorProvider`)
|
package/dist/index.d.mts
CHANGED
|
@@ -141,6 +141,7 @@ interface SelectProfilePayload<TSeat> {
|
|
|
141
141
|
}
|
|
142
142
|
interface ProfileSelectionActions<TSeat> {
|
|
143
143
|
clearProfile: ActionCreator<string>;
|
|
144
|
+
pruneToRoster: ActionCreator<string[]>;
|
|
144
145
|
select: ActionCreator<SelectProfilePayload<TSeat>>;
|
|
145
146
|
}
|
|
146
147
|
type ProfileSelectionState<TSeat extends string | number> = Record<TSeat, string | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -141,6 +141,7 @@ interface SelectProfilePayload<TSeat> {
|
|
|
141
141
|
}
|
|
142
142
|
interface ProfileSelectionActions<TSeat> {
|
|
143
143
|
clearProfile: ActionCreator<string>;
|
|
144
|
+
pruneToRoster: ActionCreator<string[]>;
|
|
144
145
|
select: ActionCreator<SelectProfilePayload<TSeat>>;
|
|
145
146
|
}
|
|
146
147
|
type ProfileSelectionState<TSeat extends string | number> = Record<TSeat, string | null>;
|
package/dist/index.js
CHANGED
|
@@ -607,6 +607,7 @@ var REHYDRATE = "persist/REHYDRATE";
|
|
|
607
607
|
function createProfileSelectionSlice(namespace, defaultState, mountKey = "profileSelection") {
|
|
608
608
|
const select = createActionCreator(`profileSelection/${namespace}/select`);
|
|
609
609
|
const clearProfile = createActionCreator(`profileSelection/${namespace}/clearProfile`);
|
|
610
|
+
const pruneToRoster = createActionCreator(`profileSelection/${namespace}/pruneToRoster`);
|
|
610
611
|
function reducer(state = defaultState, action) {
|
|
611
612
|
if (action.type === REHYDRATE) {
|
|
612
613
|
const persisted = action.payload?.[mountKey];
|
|
@@ -620,9 +621,22 @@ function createProfileSelectionSlice(namespace, defaultState, mountKey = "profil
|
|
|
620
621
|
}
|
|
621
622
|
return next;
|
|
622
623
|
}
|
|
624
|
+
if (pruneToRoster.match(action)) {
|
|
625
|
+
const roster = new Set(action.payload);
|
|
626
|
+
const next = { ...state };
|
|
627
|
+
let changed = false;
|
|
628
|
+
for (const seat of Object.keys(state)) {
|
|
629
|
+
const profileId = state[seat];
|
|
630
|
+
if (profileId !== null && !roster.has(profileId)) {
|
|
631
|
+
next[seat] = null;
|
|
632
|
+
changed = true;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return changed ? next : state;
|
|
636
|
+
}
|
|
623
637
|
return state;
|
|
624
638
|
}
|
|
625
|
-
return { actions: { clearProfile, select }, reducer };
|
|
639
|
+
return { actions: { clearProfile, pruneToRoster, select }, reducer };
|
|
626
640
|
}
|
|
627
641
|
|
|
628
642
|
// src/redux/profilesSlice.ts
|
package/dist/index.mjs
CHANGED
|
@@ -566,6 +566,7 @@ var REHYDRATE = "persist/REHYDRATE";
|
|
|
566
566
|
function createProfileSelectionSlice(namespace, defaultState, mountKey = "profileSelection") {
|
|
567
567
|
const select = createActionCreator(`profileSelection/${namespace}/select`);
|
|
568
568
|
const clearProfile = createActionCreator(`profileSelection/${namespace}/clearProfile`);
|
|
569
|
+
const pruneToRoster = createActionCreator(`profileSelection/${namespace}/pruneToRoster`);
|
|
569
570
|
function reducer(state = defaultState, action) {
|
|
570
571
|
if (action.type === REHYDRATE) {
|
|
571
572
|
const persisted = action.payload?.[mountKey];
|
|
@@ -579,9 +580,22 @@ function createProfileSelectionSlice(namespace, defaultState, mountKey = "profil
|
|
|
579
580
|
}
|
|
580
581
|
return next;
|
|
581
582
|
}
|
|
583
|
+
if (pruneToRoster.match(action)) {
|
|
584
|
+
const roster = new Set(action.payload);
|
|
585
|
+
const next = { ...state };
|
|
586
|
+
let changed = false;
|
|
587
|
+
for (const seat of Object.keys(state)) {
|
|
588
|
+
const profileId = state[seat];
|
|
589
|
+
if (profileId !== null && !roster.has(profileId)) {
|
|
590
|
+
next[seat] = null;
|
|
591
|
+
changed = true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return changed ? next : state;
|
|
595
|
+
}
|
|
582
596
|
return state;
|
|
583
597
|
}
|
|
584
|
-
return { actions: { clearProfile, select }, reducer };
|
|
598
|
+
return { actions: { clearProfile, pruneToRoster, select }, reducer };
|
|
585
599
|
}
|
|
586
600
|
|
|
587
601
|
// src/redux/profilesSlice.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Player profile identity for local-multiplayer React Native games — a saved name/color/tag a player selects per seat, with a picker, an inline-editable roster screen, and a reusable identity badge, deliberately blind to any app-specific fields (control scheme, stats) an app layers on top",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -14,6 +14,7 @@ export interface SelectProfilePayload<TSeat> {
|
|
|
14
14
|
|
|
15
15
|
export interface ProfileSelectionActions<TSeat> {
|
|
16
16
|
clearProfile: ActionCreator<string>
|
|
17
|
+
pruneToRoster: ActionCreator<string[]>
|
|
17
18
|
select: ActionCreator<SelectProfilePayload<TSeat>>
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -50,9 +51,15 @@ export interface ProfileSelectionSlice<TSeat extends string | number> {
|
|
|
50
51
|
// from inside its own reducer function — only worth overriding if some future consumer ever mounts
|
|
51
52
|
// this under a different key. Note `namespace` above is a different thing: it's the app's own name
|
|
52
53
|
// (e.g. 'airhockey', 'boxhockey'), used only to prefix action types, not the mount key.
|
|
54
|
+
//
|
|
55
|
+
// `pruneToRoster(ids)` nulls every seat whose profile id is no longer in the given roster. Call it
|
|
56
|
+
// after any profilesActions.setAll (initial reconcile, onRemoteChange), since a sibling app can
|
|
57
|
+
// delete a profile without this app's own deleteProfile ever running. It returns the same state
|
|
58
|
+
// object when nothing changes, so dispatching it on every foreground resume costs no re-render.
|
|
53
59
|
export function createProfileSelectionSlice<TSeat extends string | number>(namespace: string, defaultState: ProfileSelectionState<TSeat>, mountKey = 'profileSelection'): ProfileSelectionSlice<TSeat> {
|
|
54
60
|
const select = createActionCreator<SelectProfilePayload<TSeat>>(`profileSelection/${namespace}/select`)
|
|
55
61
|
const clearProfile = createActionCreator<string>(`profileSelection/${namespace}/clearProfile`)
|
|
62
|
+
const pruneToRoster = createActionCreator<string[]>(`profileSelection/${namespace}/pruneToRoster`)
|
|
56
63
|
|
|
57
64
|
function reducer(state: ProfileSelectionState<TSeat> = defaultState, action: { type: string; payload?: unknown }): ProfileSelectionState<TSeat> {
|
|
58
65
|
// redux-persist's own default stateReconciler (autoMergeLevel1) HARD-REPLACES this slice's
|
|
@@ -74,8 +81,21 @@ export function createProfileSelectionSlice<TSeat extends string | number>(names
|
|
|
74
81
|
}
|
|
75
82
|
return next
|
|
76
83
|
}
|
|
84
|
+
if (pruneToRoster.match(action)) {
|
|
85
|
+
const roster = new Set(action.payload)
|
|
86
|
+
const next = { ...state }
|
|
87
|
+
let changed = false
|
|
88
|
+
for (const seat of Object.keys(state) as TSeat[]) {
|
|
89
|
+
const profileId = state[seat]
|
|
90
|
+
if (profileId !== null && !roster.has(profileId)) {
|
|
91
|
+
next[seat] = null
|
|
92
|
+
changed = true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return changed ? next : state
|
|
96
|
+
}
|
|
77
97
|
return state
|
|
78
98
|
}
|
|
79
99
|
|
|
80
|
-
return { actions: { clearProfile, select }, reducer }
|
|
100
|
+
return { actions: { clearProfile, pruneToRoster, select }, reducer }
|
|
81
101
|
}
|