@tastic/profile 0.14.1 → 0.15.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/dist/index.mjs CHANGED
@@ -5,6 +5,14 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
+ // src/PickerMenu.tsx
9
+ import { defaultColors, FadingScrollView, getContrastColor as getContrastColor2 } from "@rific/auto-paper";
10
+ import { TouchableRipple } from "@rific/feedback-press";
11
+ import { PopoverBody, useAutoAlign } from "@tastic/hud";
12
+ import { useState } from "react";
13
+ import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
14
+ import { Icon as Icon2, Text as Text2 } from "react-native-paper";
15
+
8
16
  // src/IdentityChip.tsx
9
17
  import { getContrastColor } from "@rific/auto-paper";
10
18
  import { StyleSheet, View } from "react-native";
@@ -27,36 +35,8 @@ var styles = StyleSheet.create({
27
35
  }
28
36
  });
29
37
 
30
- // src/ProfileColorContext.tsx
31
- import { createContext, useContext } from "react";
32
- import { jsx as jsx2 } from "react/jsx-runtime";
33
- var ProfileColorContext = createContext(void 0);
34
- function ProfileColorProvider({ preview, children }) {
35
- return /* @__PURE__ */ jsx2(ProfileColorContext.Provider, { value: preview, children });
36
- }
37
- function useProfileColorPreview() {
38
- return useContext(ProfileColorContext);
39
- }
40
- function useProfileDisplayColor(hex) {
41
- const preview = useProfileColorPreview();
42
- return preview ? preview(hex) : hex;
43
- }
44
-
45
- // src/ProfileChip.tsx
46
- import { jsx as jsx3 } from "react/jsx-runtime";
47
- function ProfileChip({ profile, size = 22, filled = true }) {
48
- const color = useProfileDisplayColor(profile.color);
49
- return /* @__PURE__ */ jsx3(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
50
- }
51
-
52
- // src/ProfilePicker.tsx
53
- import { defaultColors, FadingScrollView, getContrastColor as getContrastColor2 } from "@rific/auto-paper";
54
- import { TouchableRipple } from "@rific/feedback-press";
55
- import { PopoverBody, useAutoAlign } from "@tastic/hud";
56
- import { useMemo, useState } from "react";
57
- import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
58
- import { Icon as Icon2, Text as Text2 } from "react-native-paper";
59
- import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
38
+ // src/PickerMenu.tsx
39
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
60
40
  var TRIGGER_HEIGHT = 28;
61
41
  var POPOVER_WIDTH = 220;
62
42
  var ROW_HEIGHT = 36;
@@ -83,55 +63,37 @@ function colorPageHeight(swatches) {
83
63
  const rows = Math.ceil(swatches.length / GRID_COLUMNS);
84
64
  return ROW_HEIGHT + GRID_TOP + rows * SWATCH_SIZE + (rows - 1) * GRID_GAP + GRID_BOTTOM;
85
65
  }
86
- function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
87
- const rowCount = profileCount + 1 + (hasManageRow ? 1 : 0);
66
+ function pickerContentSize(rowCount, colorSection) {
88
67
  const list = rowCount * ROW_HEIGHT + (colorSection ? colorRowHeight(colorSection.caption) + COLOR_ROW_DIVIDER : 0);
89
68
  const content = colorSection ? Math.max(list, colorPageHeight(colorSection.swatches ?? defaultColors)) : list;
90
69
  return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + content };
91
70
  }
92
- function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
93
- const id = `${idPrefix}-profile`;
94
- const colorPreview = useProfileColorPreview();
71
+ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColor, contentSize, align: forcedAlign, alignOverride, rotation, triggerStyle, footer, children }) {
95
72
  const menuBg = dark ? "#000000" : "#FFFFFF";
96
73
  const fg = dark ? "#FFFFFF" : "#000000";
97
74
  const open = host.openId === id;
98
- const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
99
- const sortedProfiles = useMemo(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
100
- const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
101
- const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
75
+ const auto = useAutoAlign(open, contentSize.width, contentSize.height, rotation);
102
76
  const placement = alignOverride ?? auto;
103
77
  const { maxHeight, measured, triggerRef, verticalAlign } = placement;
104
78
  const align = forcedAlign ?? placement.align;
105
- const handleSelectGuest = () => {
106
- onSelect(null);
107
- host.close();
108
- };
109
- const handleSelectExisting = (profile) => {
110
- if (profile.id === takenId) return;
111
- onSelect(profile);
112
- host.close();
113
- };
114
- const handleManage = () => {
115
- host.close();
116
- onManage?.();
117
- };
118
- const chipTrigger = !!colorSection;
79
+ const chipTrigger = !!chip;
119
80
  const [triggerWidth, setTriggerWidth] = useState(0);
120
81
  const handleAnchorLayout = chipTrigger ? (e) => setTriggerWidth(e.nativeEvent.layout.width) : void 0;
121
82
  const [colorPage, setColorPage] = useState(false);
83
+ const showColorPage = !!colorSection && colorPage;
122
84
  const handleTriggerPress = () => {
123
85
  setColorPage(false);
124
86
  host.toggle(id);
125
87
  };
126
- const triggerLabel = (selectedProfile?.name ?? guestLabel).toUpperCase();
127
- const manageFooter = !!onManage && !(colorSection && colorPage);
88
+ const triggerLabel = label.toUpperCase();
89
+ const showFooter = !!footer && !showColorPage;
128
90
  return /* @__PURE__ */ jsxs(View2, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
129
- /* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles2.trigger, chipTrigger && styles2.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ jsxs(View2, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
130
- chipTrigger && /* @__PURE__ */ jsx4(IdentityChip, { color, tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon, size: CHIP_SIZE }),
131
- /* @__PURE__ */ jsx4(Text2, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
132
- /* @__PURE__ */ jsx4(Icon2, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
91
+ /* @__PURE__ */ jsx2(TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles2.trigger, chipTrigger && styles2.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ jsxs(View2, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
92
+ chip && /* @__PURE__ */ jsx2(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
93
+ /* @__PURE__ */ jsx2(Text2, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
94
+ /* @__PURE__ */ jsx2(Icon2, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
133
95
  ] }) }),
134
- /* @__PURE__ */ jsx4(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ jsxs(
96
+ /* @__PURE__ */ jsx2(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ jsxs(
135
97
  View2,
136
98
  {
137
99
  style: [
@@ -144,77 +106,81 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
144
106
  }
145
107
  ],
146
108
  children: [
147
- /* @__PURE__ */ jsx4(FadingScrollView, { contentContainerStyle: [styles2.menuContent, manageFooter && styles2.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ jsx4(ColorPage, { section: colorSection, profileColor: selectedProfile?.color, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
148
- colorSection && /* @__PURE__ */ jsx4(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
149
- /* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ jsxs(View2, { style: styles2.rowInner, children: [
150
- /* @__PURE__ */ jsx4(View2, { style: styles2.guestChip, children: /* @__PURE__ */ jsx4(Icon2, { source: nullIcon, size: 13, color: selectedId === null ? getContrastColor2(color) : fg }) }),
151
- /* @__PURE__ */ jsx4(Text2, { style: [styles2.rowLabel, { color: selectedId === null ? getContrastColor2(color) : fg }], children: nullLabel })
152
- ] }) }),
153
- sortedProfiles.map((profile) => {
154
- const taken = profile.id === takenId;
155
- const selected = selectedId === profile.id;
156
- const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
157
- return /* @__PURE__ */ jsx4(TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ jsxs(View2, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
158
- /* @__PURE__ */ jsx4(ProfileChip, { profile, filled: !selected }),
159
- /* @__PURE__ */ jsx4(
160
- Text2,
161
- {
162
- style: [
163
- styles2.rowLabel,
164
- {
165
- color: selected ? getContrastColor2(rowColor) : fg
166
- }
167
- ],
168
- numberOfLines: 1,
169
- children: profile.name
170
- }
171
- )
172
- ] }) }, profile.id);
173
- })
174
- ] }) }, colorSection && colorPage ? "color" : "list"),
175
- manageFooter && /* @__PURE__ */ jsxs(View2, { style: styles2.menuFooter, children: [
176
- /* @__PURE__ */ jsx4(View2, { style: [styles2.divider, { backgroundColor: color }] }),
177
- /* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ jsxs(View2, { style: styles2.manageRowInner, children: [
178
- /* @__PURE__ */ jsx4(Icon2, { source: "account-cog-outline", size: 16, color }),
179
- /* @__PURE__ */ jsx4(Text2, { style: [styles2.manageLabel, { color: fg }], children: "Manage" })
180
- ] }) })
109
+ /* @__PURE__ */ jsx2(FadingScrollView, { contentContainerStyle: [styles2.menuContent, showFooter && styles2.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ jsx2(ColorPage, { section: colorSection, homeColor, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
110
+ colorSection && /* @__PURE__ */ jsx2(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
111
+ children
112
+ ] }) }, showColorPage ? "color" : "list"),
113
+ showFooter && /* @__PURE__ */ jsxs(View2, { style: styles2.menuFooter, children: [
114
+ /* @__PURE__ */ jsx2(View2, { style: [styles2.divider, { backgroundColor: color }] }),
115
+ footer
181
116
  ] })
182
117
  ]
183
118
  }
184
119
  ) })
185
120
  ] });
186
121
  }
122
+ function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }) {
123
+ const textColor = selected ? getContrastColor2(color) : fg;
124
+ return /* @__PURE__ */ jsx2(TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs(View2, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
125
+ /* @__PURE__ */ jsx2(View2, { style: styles2.iconSlot, children: /* @__PURE__ */ jsx2(Icon2, { source: icon, size: 13, color: textColor }) }),
126
+ /* @__PURE__ */ jsx2(Text2, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
127
+ ] }) });
128
+ }
187
129
  function ColorRow({ section, fg, dividerColor, onPress }) {
188
130
  const { value, previewValue, label = "Color", caption } = section;
189
131
  const drawn = previewValue ? previewValue(value) : value;
190
132
  return /* @__PURE__ */ jsxs(Fragment, { children: [
191
- /* @__PURE__ */ jsx4(TouchableRipple, { onPress, style: [styles2.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ jsxs(View2, { style: styles2.rowInner, children: [
192
- /* @__PURE__ */ jsx4(View2, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
133
+ /* @__PURE__ */ jsx2(TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ jsxs(View2, { style: pickerMenuStyles.rowInner, children: [
134
+ /* @__PURE__ */ jsx2(View2, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
193
135
  /* @__PURE__ */ jsxs(View2, { style: styles2.colorRowText, children: [
194
- /* @__PURE__ */ jsx4(Text2, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
195
- caption ? /* @__PURE__ */ jsx4(Text2, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
136
+ /* @__PURE__ */ jsx2(Text2, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
137
+ caption ? /* @__PURE__ */ jsx2(Text2, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
196
138
  ] }),
197
- /* @__PURE__ */ jsx4(Icon2, { source: "chevron-right", size: 18, color: fg })
139
+ /* @__PURE__ */ jsx2(Icon2, { source: "chevron-right", size: 18, color: fg })
198
140
  ] }) }),
199
- /* @__PURE__ */ jsx4(View2, { style: [styles2.divider, { backgroundColor: dividerColor }] })
141
+ /* @__PURE__ */ jsx2(View2, { style: [styles2.divider, { backgroundColor: dividerColor }] })
200
142
  ] });
201
143
  }
202
- function ColorPage({ section, profileColor, fg, onBack }) {
203
- const { value, onChange, previewValue, swatches = defaultColors, label = "Color" } = section;
144
+ function ColorPage({ section, homeColor, fg, onBack }) {
145
+ const { value, onChange, previewValue, swatches = defaultColors, label = "Color", takenValue, allowSwapTaken } = section;
204
146
  const displayFor = previewValue ?? ((hex) => hex);
205
147
  return /* @__PURE__ */ jsxs(Fragment, { children: [
206
- /* @__PURE__ */ jsx4(TouchableRipple, { onPress: onBack, style: styles2.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ jsxs(View2, { style: styles2.rowInner, children: [
207
- /* @__PURE__ */ jsx4(View2, { style: styles2.guestChip, children: /* @__PURE__ */ jsx4(Icon2, { source: "chevron-left", size: 20, color: fg }) }),
208
- /* @__PURE__ */ jsx4(Text2, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
148
+ /* @__PURE__ */ jsx2(TouchableRipple, { onPress: onBack, style: pickerMenuStyles.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ jsxs(View2, { style: pickerMenuStyles.rowInner, children: [
149
+ /* @__PURE__ */ jsx2(View2, { style: styles2.iconSlot, children: /* @__PURE__ */ jsx2(Icon2, { source: "chevron-left", size: 20, color: fg }) }),
150
+ /* @__PURE__ */ jsx2(Text2, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
209
151
  ] }) }),
210
- /* @__PURE__ */ jsx4(View2, { style: styles2.swatches, children: swatches.map((swatch) => {
152
+ /* @__PURE__ */ jsx2(View2, { style: styles2.swatches, children: swatches.map((swatch) => {
211
153
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
212
- const isProfileColor = !!profileColor && swatch.value.toLowerCase() === profileColor.toLowerCase();
154
+ const isHomeColor = !!homeColor && swatch.value.toLowerCase() === homeColor.toLowerCase();
155
+ const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
156
+ const swappable = taken && !!allowSwapTaken;
213
157
  const swatchDisplay = displayFor(swatch.value);
214
- return /* @__PURE__ */ jsx4(TouchableRipple, { onPress: () => onChange(swatch.value), borderless: true, style: [styles2.swatch, { backgroundColor: swatchDisplay }, selected && styles2.swatchSelected], accessibilityRole: "button", accessibilityLabel: isProfileColor ? `${swatch.label}, profile color` : swatch.label, accessibilityState: { selected }, children: selected ? /* @__PURE__ */ jsx4(Icon2, { source: "check", size: 16, color: getContrastColor2(swatchDisplay) }) : isProfileColor ? /* @__PURE__ */ jsx4(View2, { style: [styles2.profileDot, { backgroundColor: getContrastColor2(swatchDisplay) }] }) : /* @__PURE__ */ jsx4(View2, {}) }, swatch.value);
158
+ const contrast = getContrastColor2(swatchDisplay);
159
+ const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
160
+ return /* @__PURE__ */ jsx2(TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles2.swatch, { backgroundColor: swatchDisplay }, selected && styles2.swatchSelected, taken && !swappable && styles2.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: selected ? /* @__PURE__ */ jsx2(Icon2, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ jsx2(Icon2, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ jsx2(Icon2, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ jsx2(View2, { style: [styles2.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ jsx2(View2, {}) }, swatch.value);
215
161
  }) })
216
162
  ] });
217
163
  }
164
+ var pickerMenuStyles = StyleSheet2.create({
165
+ row: {
166
+ borderRadius: 8,
167
+ height: ROW_HEIGHT
168
+ },
169
+ rowDisabled: {
170
+ opacity: 0.35
171
+ },
172
+ rowInner: {
173
+ alignItems: "center",
174
+ flexDirection: "row",
175
+ flex: 1,
176
+ gap: 8,
177
+ paddingHorizontal: ROW_INSET
178
+ },
179
+ rowLabel: {
180
+ flex: 1,
181
+ fontSize: 14
182
+ }
183
+ });
218
184
  var styles2 = StyleSheet2.create({
219
185
  anchor: {
220
186
  position: "relative"
@@ -258,38 +224,31 @@ var styles2 = StyleSheet2.create({
258
224
  colorRowText: {
259
225
  flex: 1
260
226
  },
261
- // Separates Manage (a navigation action) from the selectable rows above it - different kind of
262
- // action, not just another row in the same list. Tinted with the seat's own color (passed in as
263
- // backgroundColor) rather than a flat gray, matching the border/caret's own accent-tinted look.
227
+ // Separates the footer (a navigation action) and the Color row from the selectable rows - a
228
+ // different kind of action, not just another row in the same list. Tinted with the seat's own color
229
+ // (passed in as backgroundColor) rather than a flat gray, matching the border/caret's own
230
+ // accent-tinted look.
264
231
  divider: {
265
232
  height: 1,
266
233
  marginVertical: 4,
267
234
  opacity: 0.25
268
235
  },
269
- // Matches ProfileChip's own default size/shape exactly (see this row's own render-site comment)
270
- // - no backgroundColor, unlike ProfileChip's own default: this row has no identity color of its
271
- // own to fill it with, and the render site now colors the icon directly instead.
272
- guestChip: {
236
+ // The selected profile's own color (see ColorPage).
237
+ homeDot: {
238
+ borderRadius: 4,
239
+ height: 8,
240
+ width: 8
241
+ },
242
+ // Matches ProfileChip's own default size/shape exactly (see IconRow's own render-site comment) -
243
+ // no backgroundColor, unlike ProfileChip's own default: an icon row has no identity color of its
244
+ // own to fill it with, and the render site colors the icon directly instead.
245
+ iconSlot: {
273
246
  alignItems: "center",
274
247
  borderRadius: 11,
275
248
  height: 22,
276
249
  justifyContent: "center",
277
250
  width: 22
278
251
  },
279
- manageLabel: {
280
- fontSize: 13,
281
- fontWeight: "700",
282
- letterSpacing: 0.5
283
- },
284
- // Centered rather than left-aligned like a normal row - see this row's own render-site comment
285
- // for why (a footer action, not another selectable identity).
286
- manageRowInner: {
287
- alignItems: "center",
288
- flexDirection: "row",
289
- gap: 6,
290
- height: "100%",
291
- justifyContent: "center"
292
- },
293
252
  menu: {
294
253
  borderRadius: 12,
295
254
  borderWidth: MENU_BORDER_WIDTH,
@@ -305,7 +264,7 @@ var styles2 = StyleSheet2.create({
305
264
  menuContent: {
306
265
  padding: LIST_PADDING
307
266
  },
308
- // With Manage pinned below, its own padding takes the bottom edge (same total height as before).
267
+ // With a footer pinned below, its own padding takes the bottom edge (same total height as before).
309
268
  menuContentAboveFooter: {
310
269
  paddingBottom: 0
311
270
  },
@@ -313,30 +272,6 @@ var styles2 = StyleSheet2.create({
313
272
  paddingBottom: LIST_PADDING,
314
273
  paddingHorizontal: LIST_PADDING
315
274
  },
316
- // The selected profile's own color (see ColorSection).
317
- profileDot: {
318
- borderRadius: 4,
319
- height: 8,
320
- width: 8
321
- },
322
- row: {
323
- borderRadius: 8,
324
- height: ROW_HEIGHT
325
- },
326
- rowDisabled: {
327
- opacity: 0.35
328
- },
329
- rowInner: {
330
- alignItems: "center",
331
- flexDirection: "row",
332
- flex: 1,
333
- gap: 8,
334
- paddingHorizontal: ROW_INSET
335
- },
336
- rowLabel: {
337
- flex: 1,
338
- fontSize: 14
339
- },
340
275
  swatch: {
341
276
  alignItems: "center",
342
277
  borderRadius: SWATCH_SIZE / 2,
@@ -348,6 +283,9 @@ var styles2 = StyleSheet2.create({
348
283
  borderColor: "#ffffff",
349
284
  borderWidth: 2
350
285
  },
286
+ swatchTaken: {
287
+ opacity: 0.35
288
+ },
351
289
  swatches: {
352
290
  columnGap: GRID_GAP,
353
291
  flexDirection: "row",
@@ -374,15 +312,128 @@ var styles2 = StyleSheet2.create({
374
312
  }
375
313
  });
376
314
 
377
- // src/ProfilesManager.tsx
378
- import { defaultColors as defaultColors2, useAutoPaperTheme } from "@rific/auto-paper";
315
+ // src/OptionPicker.tsx
316
+ import { jsx as jsx3 } from "react/jsx-runtime";
317
+ function getOptionPickerContentSize(optionCount, colorSection) {
318
+ return pickerContentSize(optionCount, colorSection);
319
+ }
320
+ function OptionPicker({ id, host, options, value, onChange, color, dark, icon, rowIcon = icon, colorSection, align, alignOverride, rotation = 0, triggerStyle }) {
321
+ const fg = dark ? "#FFFFFF" : "#000000";
322
+ const selected = options.find((option) => option.value === value);
323
+ const handleSelect = (option) => {
324
+ onChange(option.value);
325
+ host.close();
326
+ };
327
+ return /* @__PURE__ */ jsx3(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ jsx3(IconRow, { icon: option.icon ?? rowIcon, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
328
+ }
329
+
330
+ // src/ProfileColorContext.tsx
331
+ import { createContext, useContext } from "react";
332
+ import { jsx as jsx4 } from "react/jsx-runtime";
333
+ var ProfileColorContext = createContext(void 0);
334
+ function ProfileColorProvider({ preview, children }) {
335
+ return /* @__PURE__ */ jsx4(ProfileColorContext.Provider, { value: preview, children });
336
+ }
337
+ function useProfileColorPreview() {
338
+ return useContext(ProfileColorContext);
339
+ }
340
+ function useProfileDisplayColor(hex) {
341
+ const preview = useProfileColorPreview();
342
+ return preview ? preview(hex) : hex;
343
+ }
344
+
345
+ // src/ProfileChip.tsx
346
+ import { jsx as jsx5 } from "react/jsx-runtime";
347
+ function ProfileChip({ profile, size = 22, filled = true }) {
348
+ const color = useProfileDisplayColor(profile.color);
349
+ return /* @__PURE__ */ jsx5(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
350
+ }
351
+
352
+ // src/ProfilePicker.tsx
353
+ import { getContrastColor as getContrastColor3 } from "@rific/auto-paper";
379
354
  import { TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
355
+ import { useMemo } from "react";
356
+ import { StyleSheet as StyleSheet3, View as View3 } from "react-native";
357
+ import { Icon as Icon3, Text as Text3 } from "react-native-paper";
358
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
359
+ function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
360
+ return pickerContentSize(profileCount + 1 + (hasManageRow ? 1 : 0), colorSection);
361
+ }
362
+ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
363
+ const id = `${idPrefix}-profile`;
364
+ const colorPreview = useProfileColorPreview();
365
+ const fg = dark ? "#FFFFFF" : "#000000";
366
+ const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
367
+ const sortedProfiles = useMemo(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
368
+ const contentSize = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
369
+ const handleSelectGuest = () => {
370
+ onSelect(null);
371
+ host.close();
372
+ };
373
+ const handleSelectExisting = (profile) => {
374
+ if (profile.id === takenId) return;
375
+ onSelect(profile);
376
+ host.close();
377
+ };
378
+ const handleManage = () => {
379
+ host.close();
380
+ onManage?.();
381
+ };
382
+ const manageFooter = onManage ? /* @__PURE__ */ jsx6(TouchableRipple2, { onPress: handleManage, style: pickerMenuStyles.row, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.manageRowInner, children: [
383
+ /* @__PURE__ */ jsx6(Icon3, { source: "account-cog-outline", size: 16, color }),
384
+ /* @__PURE__ */ jsx6(Text3, { style: [styles3.manageLabel, { color: fg }], children: "Manage" })
385
+ ] }) }) : void 0;
386
+ return /* @__PURE__ */ jsxs2(PickerMenu, { id, host, color, dark, label: selectedProfile?.name ?? guestLabel, chip: colorSection ? { tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon } : void 0, colorSection, homeColor: selectedProfile?.color, contentSize, align, alignOverride, rotation, triggerStyle, footer: manageFooter, children: [
387
+ /* @__PURE__ */ jsx6(IconRow, { icon: nullIcon, label: nullLabel, selected: selectedId === null, color, fg, onPress: handleSelectGuest }),
388
+ sortedProfiles.map((profile) => {
389
+ const taken = profile.id === takenId;
390
+ const selected = selectedId === profile.id;
391
+ const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
392
+ return /* @__PURE__ */ jsx6(TouchableRipple2, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [pickerMenuStyles.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ jsxs2(View3, { style: [pickerMenuStyles.rowInner, taken && pickerMenuStyles.rowDisabled], children: [
393
+ /* @__PURE__ */ jsx6(ProfileChip, { profile, filled: !selected }),
394
+ /* @__PURE__ */ jsx6(
395
+ Text3,
396
+ {
397
+ style: [
398
+ pickerMenuStyles.rowLabel,
399
+ {
400
+ color: selected ? getContrastColor3(rowColor) : fg
401
+ }
402
+ ],
403
+ numberOfLines: 1,
404
+ children: profile.name
405
+ }
406
+ )
407
+ ] }) }, profile.id);
408
+ })
409
+ ] });
410
+ }
411
+ var styles3 = StyleSheet3.create({
412
+ manageLabel: {
413
+ fontSize: 13,
414
+ fontWeight: "700",
415
+ letterSpacing: 0.5
416
+ },
417
+ // Centered rather than left-aligned like a normal row - see the footer's own comment for why (a
418
+ // footer action, not another selectable identity).
419
+ manageRowInner: {
420
+ alignItems: "center",
421
+ flexDirection: "row",
422
+ gap: 6,
423
+ height: "100%",
424
+ justifyContent: "center"
425
+ }
426
+ });
427
+
428
+ // src/ProfilesManager.tsx
429
+ import { defaultColors as defaultColors2, FadingScrollView as FadingScrollView2, useAutoPaperTheme } from "@rific/auto-paper";
430
+ import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
380
431
  import { useFocusChain } from "@rific/focus-chain";
381
432
  import { useToast } from "@rific/toaster";
382
433
  import { ConfirmDialog, getInlineColorPickerContentSize, InlineColorPicker, usePopoverHost } from "@tastic/hud";
383
434
  import { useCallback, useEffect, useImperativeHandle, useMemo as useMemo2, useRef, useState as useState2 } from "react";
384
- import { KeyboardAvoidingView, Platform, ScrollView, StyleSheet as StyleSheet3, useWindowDimensions, View as View3 } from "react-native";
385
- import { Icon as Icon3, Text as Text3, TextInput } from "react-native-paper";
435
+ import { KeyboardAvoidingView, Platform, StyleSheet as StyleSheet4, useWindowDimensions, View as View4 } from "react-native";
436
+ import { Icon as Icon4, Text as Text4, TextInput } from "react-native-paper";
386
437
  import { useSafeAreaInsets } from "react-native-safe-area-context";
387
438
 
388
439
  // src/profilesValidation.ts
@@ -418,7 +469,7 @@ function isValidProfile(value) {
418
469
  }
419
470
 
420
471
  // src/ProfilesManager.tsx
421
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
472
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
422
473
  var CHIP_SIZE2 = 40;
423
474
  function deriveTag(name) {
424
475
  const trimmed = name.trim();
@@ -495,38 +546,38 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
495
546
  }, [confirmDeleteId, editingId, onDelete]);
496
547
  const sortedProfiles = useMemo2(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
497
548
  const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
498
- return /* @__PURE__ */ jsxs2(Fragment2, { children: [
499
- /* @__PURE__ */ jsxs2(View3, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
549
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
550
+ /* @__PURE__ */ jsxs3(View4, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
500
551
  headerLeft,
501
- /* @__PURE__ */ jsx5(Text3, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
552
+ /* @__PURE__ */ jsx7(Text4, { variant: titleVariant, style: [styles4.title, { color: fg }], children: "Profiles" })
502
553
  ] }),
503
- /* @__PURE__ */ jsx5(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ jsx5(ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
554
+ /* @__PURE__ */ jsx7(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles4.keyboardAvoiding, children: /* @__PURE__ */ jsx7(FadingScrollView2, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
504
555
  if (row.kind === "new") {
505
556
  if (editingId !== "new") {
506
- return /* @__PURE__ */ jsx5(TouchableRipple2, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.rowInner, children: [
507
- /* @__PURE__ */ jsx5(View3, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ jsx5(Icon3, { source: "plus", size: 20, color: fg }) }),
508
- /* @__PURE__ */ jsx5(Text3, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
557
+ return /* @__PURE__ */ jsx7(TouchableRipple3, { onPress: () => startEdit("new"), style: styles4.row, children: /* @__PURE__ */ jsxs3(View4, { style: styles4.rowInner, children: [
558
+ /* @__PURE__ */ jsx7(View4, { style: [styles4.rowChip, styles4.newChip, { borderColor: fg }], children: /* @__PURE__ */ jsx7(Icon4, { source: "plus", size: 20, color: fg }) }),
559
+ /* @__PURE__ */ jsx7(Text4, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
509
560
  ] }) }, "new");
510
561
  }
511
- return /* @__PURE__ */ jsx5(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
562
+ return /* @__PURE__ */ jsx7(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
512
563
  }
513
564
  const { profile } = row;
514
565
  if (editingId === profile.id) {
515
- return /* @__PURE__ */ jsx5(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
566
+ return /* @__PURE__ */ jsx7(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
516
567
  }
517
- return /* @__PURE__ */ jsx5(TouchableRipple2, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.rowInner, children: [
518
- /* @__PURE__ */ jsx5(ProfileChip, { profile, size: CHIP_SIZE2 }),
519
- /* @__PURE__ */ jsx5(Text3, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
568
+ return /* @__PURE__ */ jsx7(TouchableRipple3, { onPress: () => startEdit(profile), style: styles4.row, children: /* @__PURE__ */ jsxs3(View4, { style: styles4.rowInner, children: [
569
+ /* @__PURE__ */ jsx7(ProfileChip, { profile, size: CHIP_SIZE2 }),
570
+ /* @__PURE__ */ jsx7(Text4, { variant: "bodyLarge", style: [styles4.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
520
571
  ] }) }, profile.id);
521
572
  }) }) }),
522
- /* @__PURE__ */ jsx5(ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
573
+ /* @__PURE__ */ jsx7(ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
523
574
  ] });
524
575
  }
525
576
  function AlignedColorPicker({ useAlign, ...props }) {
526
577
  const { width: windowWidth } = useWindowDimensions();
527
578
  const { width, height } = getInlineColorPickerContentSize(defaultColors2.length, windowWidth);
528
579
  const alignOverride = useAlign(props.host.openId === props.id, width, height);
529
- return /* @__PURE__ */ jsx5(InlineColorPicker, { ...props, alignOverride });
580
+ return /* @__PURE__ */ jsx7(InlineColorPicker, { ...props, alignOverride });
530
581
  }
531
582
  function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
532
583
  const register = useFocusChain();
@@ -539,14 +590,14 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
539
590
  useEffect(() => {
540
591
  if (host.openId === "color") tagFocusRef.current();
541
592
  }, [host.openId]);
542
- return /* @__PURE__ */ jsxs2(View3, { style: styles3.editRow, children: [
543
- useColorPickerAlign ? /* @__PURE__ */ jsx5(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ jsx5(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
544
- /* @__PURE__ */ jsx5(TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
545
- /* @__PURE__ */ jsx5(TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles3.nameInput }),
546
- onDelete && /* @__PURE__ */ jsx5(TouchableRipple2, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ jsx5(Icon3, { source: "trash-can-outline", size: 18, color: fgMuted }) })
593
+ return /* @__PURE__ */ jsxs3(View4, { style: styles4.editRow, children: [
594
+ useColorPickerAlign ? /* @__PURE__ */ jsx7(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ jsx7(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
595
+ /* @__PURE__ */ jsx7(TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles4.hiddenTagInput, pointerEvents: "none" }),
596
+ /* @__PURE__ */ jsx7(TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles4.nameInput }),
597
+ onDelete && /* @__PURE__ */ jsx7(TouchableRipple3, { onPress: onDelete, style: styles4.iconButton, children: /* @__PURE__ */ jsx7(Icon4, { source: "trash-can-outline", size: 18, color: fgMuted }) })
547
598
  ] });
548
599
  }
549
- var styles3 = StyleSheet3.create({
600
+ var styles4 = StyleSheet4.create({
550
601
  content: {
551
602
  gap: 4,
552
603
  paddingHorizontal: 20
@@ -628,8 +679,8 @@ var styles3 = StyleSheet3.create({
628
679
  import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
629
680
  import { IconButton } from "@rific/feedback-press";
630
681
  import { useRef as useRef2 } from "react";
631
- import { StyleSheet as StyleSheet4, View as View4 } from "react-native";
632
- import { jsx as jsx6 } from "react/jsx-runtime";
682
+ import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
683
+ import { jsx as jsx8 } from "react/jsx-runtime";
633
684
  function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
634
685
  const { dark } = useAutoPaperTheme2();
635
686
  const managerRef = useRef2(null);
@@ -639,9 +690,9 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
639
690
  managerRef.current?.commitPendingEdit();
640
691
  onBack();
641
692
  };
642
- return /* @__PURE__ */ jsx6(View4, { style: [styles4.container, { backgroundColor: bg }], children: /* @__PURE__ */ jsx6(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ jsx6(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
693
+ return /* @__PURE__ */ jsx8(View5, { style: [styles5.container, { backgroundColor: bg }], children: /* @__PURE__ */ jsx8(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ jsx8(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
643
694
  }
644
- var styles4 = StyleSheet4.create({
695
+ var styles5 = StyleSheet5.create({
645
696
  container: {
646
697
  flex: 1
647
698
  }
@@ -805,21 +856,52 @@ async function resolveInitialProfiles(groupId, localFallback) {
805
856
 
806
857
  // src/redux/seatColorsSlice.ts
807
858
  var REHYDRATE2 = "persist/REHYDRATE";
859
+ function sameColor(a, b) {
860
+ return a.toLowerCase() === b.toLowerCase();
861
+ }
862
+ function profileSeatColor(profile, profileColors) {
863
+ const pick = profileColors[profile.id];
864
+ return pick && sameColor(pick.profileColor, profile.color) ? pick.color : profile.color;
865
+ }
808
866
  function createSeatColorsSlice(namespace, defaultState, mountKey = "seatColors") {
809
867
  const setLastCpuColor = createActionCreator(`seatColors/${namespace}/setLastCpuColor`);
810
868
  const setLastGuestColor = createActionCreator(`seatColors/${namespace}/setLastGuestColor`);
811
- const setProfileOverride = createActionCreator(`seatColors/${namespace}/setProfileOverride`);
869
+ const setProfileColor = createActionCreator(`seatColors/${namespace}/setProfileColor`);
870
+ function withoutPick(state, id) {
871
+ if (!state.profileColors[id]) return state;
872
+ const profileColors = { ...state.profileColors };
873
+ delete profileColors[id];
874
+ return { ...state, profileColors };
875
+ }
812
876
  function reducer(state = defaultState, action) {
813
877
  if (action.type === REHYDRATE2) {
814
878
  const persisted = action.payload?.[mountKey];
815
- return { ...defaultState, ...state, ...persisted };
879
+ const next = { ...defaultState, ...state, ...persisted };
880
+ delete next.profileOverride;
881
+ return next;
816
882
  }
817
883
  if (setLastCpuColor.match(action)) return { ...state, lastCpuColor: action.payload };
818
884
  if (setLastGuestColor.match(action)) return { ...state, lastGuestColor: { ...state.lastGuestColor, [action.payload.seat]: action.payload.color } };
819
- if (setProfileOverride.match(action)) return { ...state, profileOverride: { ...state.profileOverride, [action.payload.seat]: action.payload.color } };
885
+ if (setProfileColor.match(action)) {
886
+ const { profile, color } = action.payload;
887
+ if (color === null || sameColor(color, profile.color)) return withoutPick(state, profile.id);
888
+ return { ...state, profileColors: { ...state.profileColors, [profile.id]: { color, profileColor: profile.color } } };
889
+ }
890
+ if (profilesActions.remove.match(action)) return withoutPick(state, action.payload);
891
+ if (profilesActions.update.match(action)) {
892
+ const { id, patch } = action.payload;
893
+ const pick = state.profileColors[id];
894
+ return pick && patch.color !== void 0 && !sameColor(patch.color, pick.profileColor) ? withoutPick(state, id) : state;
895
+ }
896
+ if (profilesActions.setAll.match(action)) {
897
+ return action.payload.reduce((next, profile) => {
898
+ const pick = next.profileColors[profile.id];
899
+ return pick && !sameColor(profile.color, pick.profileColor) ? withoutPick(next, profile.id) : next;
900
+ }, state);
901
+ }
820
902
  return state;
821
903
  }
822
- return { actions: { setLastCpuColor, setLastGuestColor, setProfileOverride }, reducer };
904
+ return { actions: { setLastCpuColor, setLastGuestColor, setProfileColor }, reducer };
823
905
  }
824
906
 
825
907
  // src/redux/useSharedProfilesSync.ts
@@ -868,6 +950,7 @@ function avoidSeatColorClash(p1Color, p2Color, defaults) {
868
950
  export {
869
951
  MAX_PROFILE_NAME_LENGTH,
870
952
  MAX_TAG_LENGTH,
953
+ OptionPicker,
871
954
  ProfileChip,
872
955
  ProfileColorProvider,
873
956
  ProfilePicker,
@@ -878,11 +961,13 @@ export {
878
961
  createProfileRecord,
879
962
  createProfileSelectionSlice,
880
963
  createSeatColorsSlice,
964
+ getOptionPickerContentSize,
881
965
  getProfilePickerContentSize,
882
966
  isSharedProfileStoreAvailable,
883
967
  isValidProfile,
884
968
  isValidTag,
885
969
  loadSharedProfiles,
970
+ profileSeatColor,
886
971
  profilesActions,
887
972
  profilesReducer,
888
973
  readSharedProfiles,