@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.js CHANGED
@@ -22,6 +22,7 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  MAX_PROFILE_NAME_LENGTH: () => MAX_PROFILE_NAME_LENGTH,
24
24
  MAX_TAG_LENGTH: () => MAX_TAG_LENGTH,
25
+ OptionPicker: () => OptionPicker,
25
26
  ProfileChip: () => ProfileChip,
26
27
  ProfileColorProvider: () => ProfileColorProvider,
27
28
  ProfilePicker: () => ProfilePicker,
@@ -32,11 +33,13 @@ __export(index_exports, {
32
33
  createProfileRecord: () => createProfileRecord,
33
34
  createProfileSelectionSlice: () => createProfileSelectionSlice,
34
35
  createSeatColorsSlice: () => createSeatColorsSlice,
36
+ getOptionPickerContentSize: () => getOptionPickerContentSize,
35
37
  getProfilePickerContentSize: () => getProfilePickerContentSize,
36
38
  isSharedProfileStoreAvailable: () => isSharedProfileStoreAvailable,
37
39
  isValidProfile: () => isValidProfile,
38
40
  isValidTag: () => isValidTag,
39
41
  loadSharedProfiles: () => loadSharedProfiles,
42
+ profileSeatColor: () => profileSeatColor,
40
43
  profilesActions: () => profilesActions,
41
44
  profilesReducer: () => profilesReducer,
42
45
  readSharedProfiles: () => readSharedProfiles,
@@ -48,6 +51,14 @@ __export(index_exports, {
48
51
  });
49
52
  module.exports = __toCommonJS(index_exports);
50
53
 
54
+ // src/PickerMenu.tsx
55
+ var import_auto_paper2 = require("@rific/auto-paper");
56
+ var import_feedback_press = require("@rific/feedback-press");
57
+ var import_hud = require("@tastic/hud");
58
+ var import_react = require("react");
59
+ var import_react_native2 = require("react-native");
60
+ var import_react_native_paper2 = require("react-native-paper");
61
+
51
62
  // src/IdentityChip.tsx
52
63
  var import_auto_paper = require("@rific/auto-paper");
53
64
  var import_react_native = require("react-native");
@@ -70,36 +81,8 @@ var styles = import_react_native.StyleSheet.create({
70
81
  }
71
82
  });
72
83
 
73
- // src/ProfileColorContext.tsx
74
- var import_react = require("react");
84
+ // src/PickerMenu.tsx
75
85
  var import_jsx_runtime2 = require("react/jsx-runtime");
76
- var ProfileColorContext = (0, import_react.createContext)(void 0);
77
- function ProfileColorProvider({ preview, children }) {
78
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ProfileColorContext.Provider, { value: preview, children });
79
- }
80
- function useProfileColorPreview() {
81
- return (0, import_react.useContext)(ProfileColorContext);
82
- }
83
- function useProfileDisplayColor(hex) {
84
- const preview = useProfileColorPreview();
85
- return preview ? preview(hex) : hex;
86
- }
87
-
88
- // src/ProfileChip.tsx
89
- var import_jsx_runtime3 = require("react/jsx-runtime");
90
- function ProfileChip({ profile, size = 22, filled = true }) {
91
- const color = useProfileDisplayColor(profile.color);
92
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
93
- }
94
-
95
- // src/ProfilePicker.tsx
96
- var import_auto_paper2 = require("@rific/auto-paper");
97
- var import_feedback_press = require("@rific/feedback-press");
98
- var import_hud = require("@tastic/hud");
99
- var import_react2 = require("react");
100
- var import_react_native2 = require("react-native");
101
- var import_react_native_paper2 = require("react-native-paper");
102
- var import_jsx_runtime4 = require("react/jsx-runtime");
103
86
  var TRIGGER_HEIGHT = 28;
104
87
  var POPOVER_WIDTH = 220;
105
88
  var ROW_HEIGHT = 36;
@@ -126,55 +109,37 @@ function colorPageHeight(swatches) {
126
109
  const rows = Math.ceil(swatches.length / GRID_COLUMNS);
127
110
  return ROW_HEIGHT + GRID_TOP + rows * SWATCH_SIZE + (rows - 1) * GRID_GAP + GRID_BOTTOM;
128
111
  }
129
- function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
130
- const rowCount = profileCount + 1 + (hasManageRow ? 1 : 0);
112
+ function pickerContentSize(rowCount, colorSection) {
131
113
  const list = rowCount * ROW_HEIGHT + (colorSection ? colorRowHeight(colorSection.caption) + COLOR_ROW_DIVIDER : 0);
132
114
  const content = colorSection ? Math.max(list, colorPageHeight(colorSection.swatches ?? import_auto_paper2.defaultColors)) : list;
133
115
  return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + content };
134
116
  }
135
- 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 }) {
136
- const id = `${idPrefix}-profile`;
137
- const colorPreview = useProfileColorPreview();
117
+ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColor, contentSize, align: forcedAlign, alignOverride, rotation, triggerStyle, footer, children }) {
138
118
  const menuBg = dark ? "#000000" : "#FFFFFF";
139
119
  const fg = dark ? "#FFFFFF" : "#000000";
140
120
  const open = host.openId === id;
141
- const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
142
- const sortedProfiles = (0, import_react2.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
143
- const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
144
- const auto = (0, import_hud.useAutoAlign)(open, contentWidth, contentHeight, rotation);
121
+ const auto = (0, import_hud.useAutoAlign)(open, contentSize.width, contentSize.height, rotation);
145
122
  const placement = alignOverride ?? auto;
146
123
  const { maxHeight, measured, triggerRef, verticalAlign } = placement;
147
124
  const align = forcedAlign ?? placement.align;
148
- const handleSelectGuest = () => {
149
- onSelect(null);
150
- host.close();
151
- };
152
- const handleSelectExisting = (profile) => {
153
- if (profile.id === takenId) return;
154
- onSelect(profile);
155
- host.close();
156
- };
157
- const handleManage = () => {
158
- host.close();
159
- onManage?.();
160
- };
161
- const chipTrigger = !!colorSection;
162
- const [triggerWidth, setTriggerWidth] = (0, import_react2.useState)(0);
125
+ const chipTrigger = !!chip;
126
+ const [triggerWidth, setTriggerWidth] = (0, import_react.useState)(0);
163
127
  const handleAnchorLayout = chipTrigger ? (e) => setTriggerWidth(e.nativeEvent.layout.width) : void 0;
164
- const [colorPage, setColorPage] = (0, import_react2.useState)(false);
128
+ const [colorPage, setColorPage] = (0, import_react.useState)(false);
129
+ const showColorPage = !!colorSection && colorPage;
165
130
  const handleTriggerPress = () => {
166
131
  setColorPage(false);
167
132
  host.toggle(id);
168
133
  };
169
- const triggerLabel = (selectedProfile?.name ?? guestLabel).toUpperCase();
170
- const manageFooter = !!onManage && !(colorSection && colorPage);
171
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
172
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles2.trigger, chipTrigger && styles2.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
173
- chipTrigger && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IdentityChip, { color, tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon, size: CHIP_SIZE }),
174
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
175
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
134
+ const triggerLabel = label.toUpperCase();
135
+ const showFooter = !!footer && !showColorPage;
136
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
137
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles2.trigger, chipTrigger && styles2.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
138
+ chip && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
139
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
140
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
176
141
  ] }) }),
177
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
142
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_hud.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
178
143
  import_react_native2.View,
179
144
  {
180
145
  style: [
@@ -187,77 +152,81 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
187
152
  }
188
153
  ],
189
154
  children: [
190
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_auto_paper2.FadingScrollView, { contentContainerStyle: [styles2.menuContent, manageFooter && styles2.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorPage, { section: colorSection, profileColor: selectedProfile?.color, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
191
- colorSection && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
192
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
193
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: nullIcon, size: 13, color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }) }),
194
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.rowLabel, { color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }], children: nullLabel })
195
- ] }) }),
196
- sortedProfiles.map((profile) => {
197
- const taken = profile.id === takenId;
198
- const selected = selectedId === profile.id;
199
- const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
200
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
201
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileChip, { profile, filled: !selected }),
202
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
203
- import_react_native_paper2.Text,
204
- {
205
- style: [
206
- styles2.rowLabel,
207
- {
208
- color: selected ? (0, import_auto_paper2.getContrastColor)(rowColor) : fg
209
- }
210
- ],
211
- numberOfLines: 1,
212
- children: profile.name
213
- }
214
- )
215
- ] }) }, profile.id);
216
- })
217
- ] }) }, colorSection && colorPage ? "color" : "list"),
218
- manageFooter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.menuFooter, children: [
219
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
220
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.manageRowInner, children: [
221
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "account-cog-outline", size: 16, color }),
222
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.manageLabel, { color: fg }], children: "Manage" })
223
- ] }) })
155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.FadingScrollView, { contentContainerStyle: [styles2.menuContent, showFooter && styles2.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColorPage, { section: colorSection, homeColor, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
156
+ colorSection && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
157
+ children
158
+ ] }) }, showColorPage ? "color" : "list"),
159
+ showFooter && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.menuFooter, children: [
160
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
161
+ footer
224
162
  ] })
225
163
  ]
226
164
  }
227
165
  ) })
228
166
  ] });
229
167
  }
168
+ function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }) {
169
+ const textColor = selected ? (0, import_auto_paper2.getContrastColor)(color) : fg;
170
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: 13, color: textColor }) }),
172
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
173
+ ] }) });
174
+ }
230
175
  function ColorRow({ section, fg, dividerColor, onPress }) {
231
176
  const { value, previewValue, label = "Color", caption } = section;
232
177
  const drawn = previewValue ? previewValue(value) : value;
233
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
234
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [styles2.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
235
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
236
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.colorRowText, children: [
237
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
238
- caption ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
178
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
179
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: pickerMenuStyles.rowInner, children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.colorRowText, children: [
182
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
183
+ caption ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
239
184
  ] }),
240
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-right", size: 18, color: fg })
185
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "chevron-right", size: 18, color: fg })
241
186
  ] }) }),
242
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: dividerColor }] })
187
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: dividerColor }] })
243
188
  ] });
244
189
  }
245
- function ColorPage({ section, profileColor, fg, onBack }) {
246
- const { value, onChange, previewValue, swatches = import_auto_paper2.defaultColors, label = "Color" } = section;
190
+ function ColorPage({ section, homeColor, fg, onBack }) {
191
+ const { value, onChange, previewValue, swatches = import_auto_paper2.defaultColors, label = "Color", takenValue, allowSwapTaken } = section;
247
192
  const displayFor = previewValue ?? ((hex) => hex);
248
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
249
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: onBack, style: styles2.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
250
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-left", size: 20, color: fg }) }),
251
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
193
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
194
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: onBack, style: pickerMenuStyles.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: pickerMenuStyles.rowInner, children: [
195
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "chevron-left", size: 20, color: fg }) }),
196
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
252
197
  ] }) }),
253
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.swatches, children: swatches.map((swatch) => {
198
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.swatches, children: swatches.map((swatch) => {
254
199
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
255
- const isProfileColor = !!profileColor && swatch.value.toLowerCase() === profileColor.toLowerCase();
200
+ const isHomeColor = !!homeColor && swatch.value.toLowerCase() === homeColor.toLowerCase();
201
+ const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
202
+ const swappable = taken && !!allowSwapTaken;
256
203
  const swatchDisplay = displayFor(swatch.value);
257
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.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__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatchDisplay) }) : isProfileColor ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.profileDot, { backgroundColor: (0, import_auto_paper2.getContrastColor)(swatchDisplay) }] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, {}) }, swatch.value);
204
+ const contrast = (0, import_auto_paper2.getContrastColor)(swatchDisplay);
205
+ const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
206
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.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__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, {}) }, swatch.value);
258
207
  }) })
259
208
  ] });
260
209
  }
210
+ var pickerMenuStyles = import_react_native2.StyleSheet.create({
211
+ row: {
212
+ borderRadius: 8,
213
+ height: ROW_HEIGHT
214
+ },
215
+ rowDisabled: {
216
+ opacity: 0.35
217
+ },
218
+ rowInner: {
219
+ alignItems: "center",
220
+ flexDirection: "row",
221
+ flex: 1,
222
+ gap: 8,
223
+ paddingHorizontal: ROW_INSET
224
+ },
225
+ rowLabel: {
226
+ flex: 1,
227
+ fontSize: 14
228
+ }
229
+ });
261
230
  var styles2 = import_react_native2.StyleSheet.create({
262
231
  anchor: {
263
232
  position: "relative"
@@ -301,38 +270,31 @@ var styles2 = import_react_native2.StyleSheet.create({
301
270
  colorRowText: {
302
271
  flex: 1
303
272
  },
304
- // Separates Manage (a navigation action) from the selectable rows above it - different kind of
305
- // action, not just another row in the same list. Tinted with the seat's own color (passed in as
306
- // backgroundColor) rather than a flat gray, matching the border/caret's own accent-tinted look.
273
+ // Separates the footer (a navigation action) and the Color row from the selectable rows - a
274
+ // different kind of action, not just another row in the same list. Tinted with the seat's own color
275
+ // (passed in as backgroundColor) rather than a flat gray, matching the border/caret's own
276
+ // accent-tinted look.
307
277
  divider: {
308
278
  height: 1,
309
279
  marginVertical: 4,
310
280
  opacity: 0.25
311
281
  },
312
- // Matches ProfileChip's own default size/shape exactly (see this row's own render-site comment)
313
- // - no backgroundColor, unlike ProfileChip's own default: this row has no identity color of its
314
- // own to fill it with, and the render site now colors the icon directly instead.
315
- guestChip: {
282
+ // The selected profile's own color (see ColorPage).
283
+ homeDot: {
284
+ borderRadius: 4,
285
+ height: 8,
286
+ width: 8
287
+ },
288
+ // Matches ProfileChip's own default size/shape exactly (see IconRow's own render-site comment) -
289
+ // no backgroundColor, unlike ProfileChip's own default: an icon row has no identity color of its
290
+ // own to fill it with, and the render site colors the icon directly instead.
291
+ iconSlot: {
316
292
  alignItems: "center",
317
293
  borderRadius: 11,
318
294
  height: 22,
319
295
  justifyContent: "center",
320
296
  width: 22
321
297
  },
322
- manageLabel: {
323
- fontSize: 13,
324
- fontWeight: "700",
325
- letterSpacing: 0.5
326
- },
327
- // Centered rather than left-aligned like a normal row - see this row's own render-site comment
328
- // for why (a footer action, not another selectable identity).
329
- manageRowInner: {
330
- alignItems: "center",
331
- flexDirection: "row",
332
- gap: 6,
333
- height: "100%",
334
- justifyContent: "center"
335
- },
336
298
  menu: {
337
299
  borderRadius: 12,
338
300
  borderWidth: MENU_BORDER_WIDTH,
@@ -348,7 +310,7 @@ var styles2 = import_react_native2.StyleSheet.create({
348
310
  menuContent: {
349
311
  padding: LIST_PADDING
350
312
  },
351
- // With Manage pinned below, its own padding takes the bottom edge (same total height as before).
313
+ // With a footer pinned below, its own padding takes the bottom edge (same total height as before).
352
314
  menuContentAboveFooter: {
353
315
  paddingBottom: 0
354
316
  },
@@ -356,30 +318,6 @@ var styles2 = import_react_native2.StyleSheet.create({
356
318
  paddingBottom: LIST_PADDING,
357
319
  paddingHorizontal: LIST_PADDING
358
320
  },
359
- // The selected profile's own color (see ColorSection).
360
- profileDot: {
361
- borderRadius: 4,
362
- height: 8,
363
- width: 8
364
- },
365
- row: {
366
- borderRadius: 8,
367
- height: ROW_HEIGHT
368
- },
369
- rowDisabled: {
370
- opacity: 0.35
371
- },
372
- rowInner: {
373
- alignItems: "center",
374
- flexDirection: "row",
375
- flex: 1,
376
- gap: 8,
377
- paddingHorizontal: ROW_INSET
378
- },
379
- rowLabel: {
380
- flex: 1,
381
- fontSize: 14
382
- },
383
321
  swatch: {
384
322
  alignItems: "center",
385
323
  borderRadius: SWATCH_SIZE / 2,
@@ -391,6 +329,9 @@ var styles2 = import_react_native2.StyleSheet.create({
391
329
  borderColor: "#ffffff",
392
330
  borderWidth: 2
393
331
  },
332
+ swatchTaken: {
333
+ opacity: 0.35
334
+ },
394
335
  swatches: {
395
336
  columnGap: GRID_GAP,
396
337
  flexDirection: "row",
@@ -417,15 +358,128 @@ var styles2 = import_react_native2.StyleSheet.create({
417
358
  }
418
359
  });
419
360
 
420
- // src/ProfilesManager.tsx
361
+ // src/OptionPicker.tsx
362
+ var import_jsx_runtime3 = require("react/jsx-runtime");
363
+ function getOptionPickerContentSize(optionCount, colorSection) {
364
+ return pickerContentSize(optionCount, colorSection);
365
+ }
366
+ function OptionPicker({ id, host, options, value, onChange, color, dark, icon, rowIcon = icon, colorSection, align, alignOverride, rotation = 0, triggerStyle }) {
367
+ const fg = dark ? "#FFFFFF" : "#000000";
368
+ const selected = options.find((option) => option.value === value);
369
+ const handleSelect = (option) => {
370
+ onChange(option.value);
371
+ host.close();
372
+ };
373
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(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__ */ (0, import_jsx_runtime3.jsx)(IconRow, { icon: option.icon ?? rowIcon, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
374
+ }
375
+
376
+ // src/ProfileColorContext.tsx
377
+ var import_react2 = require("react");
378
+ var import_jsx_runtime4 = require("react/jsx-runtime");
379
+ var ProfileColorContext = (0, import_react2.createContext)(void 0);
380
+ function ProfileColorProvider({ preview, children }) {
381
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileColorContext.Provider, { value: preview, children });
382
+ }
383
+ function useProfileColorPreview() {
384
+ return (0, import_react2.useContext)(ProfileColorContext);
385
+ }
386
+ function useProfileDisplayColor(hex) {
387
+ const preview = useProfileColorPreview();
388
+ return preview ? preview(hex) : hex;
389
+ }
390
+
391
+ // src/ProfileChip.tsx
392
+ var import_jsx_runtime5 = require("react/jsx-runtime");
393
+ function ProfileChip({ profile, size = 22, filled = true }) {
394
+ const color = useProfileDisplayColor(profile.color);
395
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
396
+ }
397
+
398
+ // src/ProfilePicker.tsx
421
399
  var import_auto_paper3 = require("@rific/auto-paper");
422
400
  var import_feedback_press2 = require("@rific/feedback-press");
423
- var import_focus_chain = require("@rific/focus-chain");
424
- var import_toaster = require("@rific/toaster");
425
- var import_hud2 = require("@tastic/hud");
426
401
  var import_react3 = require("react");
427
402
  var import_react_native3 = require("react-native");
428
403
  var import_react_native_paper3 = require("react-native-paper");
404
+ var import_jsx_runtime6 = require("react/jsx-runtime");
405
+ function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
406
+ return pickerContentSize(profileCount + 1 + (hasManageRow ? 1 : 0), colorSection);
407
+ }
408
+ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
409
+ const id = `${idPrefix}-profile`;
410
+ const colorPreview = useProfileColorPreview();
411
+ const fg = dark ? "#FFFFFF" : "#000000";
412
+ const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
413
+ const sortedProfiles = (0, import_react3.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
414
+ const contentSize = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
415
+ const handleSelectGuest = () => {
416
+ onSelect(null);
417
+ host.close();
418
+ };
419
+ const handleSelectExisting = (profile) => {
420
+ if (profile.id === takenId) return;
421
+ onSelect(profile);
422
+ host.close();
423
+ };
424
+ const handleManage = () => {
425
+ host.close();
426
+ onManage?.();
427
+ };
428
+ const manageFooter = onManage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press2.TouchableRipple, { onPress: handleManage, style: pickerMenuStyles.row, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.View, { style: styles3.manageRowInner, children: [
429
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper3.Icon, { source: "account-cog-outline", size: 16, color }),
430
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper3.Text, { style: [styles3.manageLabel, { color: fg }], children: "Manage" })
431
+ ] }) }) : void 0;
432
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(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: [
433
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconRow, { icon: nullIcon, label: nullLabel, selected: selectedId === null, color, fg, onPress: handleSelectGuest }),
434
+ sortedProfiles.map((profile) => {
435
+ const taken = profile.id === takenId;
436
+ const selected = selectedId === profile.id;
437
+ const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
438
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press2.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [pickerMenuStyles.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.View, { style: [pickerMenuStyles.rowInner, taken && pickerMenuStyles.rowDisabled], children: [
439
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProfileChip, { profile, filled: !selected }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
441
+ import_react_native_paper3.Text,
442
+ {
443
+ style: [
444
+ pickerMenuStyles.rowLabel,
445
+ {
446
+ color: selected ? (0, import_auto_paper3.getContrastColor)(rowColor) : fg
447
+ }
448
+ ],
449
+ numberOfLines: 1,
450
+ children: profile.name
451
+ }
452
+ )
453
+ ] }) }, profile.id);
454
+ })
455
+ ] });
456
+ }
457
+ var styles3 = import_react_native3.StyleSheet.create({
458
+ manageLabel: {
459
+ fontSize: 13,
460
+ fontWeight: "700",
461
+ letterSpacing: 0.5
462
+ },
463
+ // Centered rather than left-aligned like a normal row - see the footer's own comment for why (a
464
+ // footer action, not another selectable identity).
465
+ manageRowInner: {
466
+ alignItems: "center",
467
+ flexDirection: "row",
468
+ gap: 6,
469
+ height: "100%",
470
+ justifyContent: "center"
471
+ }
472
+ });
473
+
474
+ // src/ProfilesManager.tsx
475
+ var import_auto_paper4 = require("@rific/auto-paper");
476
+ var import_feedback_press3 = require("@rific/feedback-press");
477
+ var import_focus_chain = require("@rific/focus-chain");
478
+ var import_toaster = require("@rific/toaster");
479
+ var import_hud2 = require("@tastic/hud");
480
+ var import_react4 = require("react");
481
+ var import_react_native4 = require("react-native");
482
+ var import_react_native_paper4 = require("react-native-paper");
429
483
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
430
484
 
431
485
  // src/profilesValidation.ts
@@ -461,7 +515,7 @@ function isValidProfile(value) {
461
515
  }
462
516
 
463
517
  // src/ProfilesManager.tsx
464
- var import_jsx_runtime5 = require("react/jsx-runtime");
518
+ var import_jsx_runtime7 = require("react/jsx-runtime");
465
519
  var CHIP_SIZE2 = 40;
466
520
  function deriveTag(name) {
467
521
  const trimmed = name.trim();
@@ -470,19 +524,19 @@ function deriveTag(name) {
470
524
  function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, titleVariant = "headlineSmall", colorPreview: colorPreviewProp, useColorPickerAlign }) {
471
525
  const contextColorPreview = useProfileColorPreview();
472
526
  const colorPreview = colorPreviewProp ?? contextColorPreview;
473
- const { dark } = (0, import_auto_paper3.useAutoPaperTheme)();
527
+ const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
474
528
  const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
475
529
  const { error: showErrorToast } = (0, import_toaster.useToast)();
476
530
  const host = (0, import_hud2.usePopoverHost)();
477
531
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
478
532
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
479
- const [editingId, setEditingId] = (0, import_react3.useState)(null);
480
- const [draftName, setDraftName] = (0, import_react3.useState)("");
481
- const [draftColor, setDraftColor] = (0, import_react3.useState)(defaultColor);
482
- const [draftTag, setDraftTag] = (0, import_react3.useState)("");
483
- const [tagManuallySet, setTagManuallySet] = (0, import_react3.useState)(false);
484
- const [confirmDeleteId, setConfirmDeleteId] = (0, import_react3.useState)(null);
485
- const commitEdit = (0, import_react3.useCallback)(() => {
533
+ const [editingId, setEditingId] = (0, import_react4.useState)(null);
534
+ const [draftName, setDraftName] = (0, import_react4.useState)("");
535
+ const [draftColor, setDraftColor] = (0, import_react4.useState)(defaultColor);
536
+ const [draftTag, setDraftTag] = (0, import_react4.useState)("");
537
+ const [tagManuallySet, setTagManuallySet] = (0, import_react4.useState)(false);
538
+ const [confirmDeleteId, setConfirmDeleteId] = (0, import_react4.useState)(null);
539
+ const commitEdit = (0, import_react4.useCallback)(() => {
486
540
  const name = draftName.trim();
487
541
  if (editingId && name) {
488
542
  const tagValid = isValidTag(draftTag);
@@ -493,13 +547,13 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
493
547
  }
494
548
  setEditingId(null);
495
549
  }, [editingId, draftName, draftColor, draftTag, onCreate, onSave, showErrorToast]);
496
- const commitEditRef = (0, import_react3.useRef)(commitEdit);
497
- (0, import_react3.useEffect)(() => {
550
+ const commitEditRef = (0, import_react4.useRef)(commitEdit);
551
+ (0, import_react4.useEffect)(() => {
498
552
  commitEditRef.current = commitEdit;
499
553
  });
500
- (0, import_react3.useEffect)(() => () => commitEditRef.current(), []);
501
- (0, import_react3.useImperativeHandle)(ref, () => ({ commitPendingEdit: () => commitEditRef.current() }), []);
502
- const startEdit = (0, import_react3.useCallback)(
554
+ (0, import_react4.useEffect)(() => () => commitEditRef.current(), []);
555
+ (0, import_react4.useImperativeHandle)(ref, () => ({ commitPendingEdit: () => commitEditRef.current() }), []);
556
+ const startEdit = (0, import_react4.useCallback)(
503
557
  (target) => {
504
558
  commitEdit();
505
559
  if (target === "new") {
@@ -517,79 +571,79 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
517
571
  },
518
572
  [commitEdit, defaultColor]
519
573
  );
520
- const handleNameChange = (0, import_react3.useCallback)(
574
+ const handleNameChange = (0, import_react4.useCallback)(
521
575
  (name) => {
522
576
  setDraftName(name);
523
577
  if (!tagManuallySet) setDraftTag(deriveTag(name));
524
578
  },
525
579
  [tagManuallySet]
526
580
  );
527
- const handleTagChange = (0, import_react3.useCallback)((tag) => {
581
+ const handleTagChange = (0, import_react4.useCallback)((tag) => {
528
582
  setDraftTag((previous) => clampTag(previous, tag));
529
583
  setTagManuallySet(true);
530
584
  }, []);
531
585
  const confirmDeleteProfile = profiles.find((p) => p.id === confirmDeleteId) ?? null;
532
- const handleConfirmDelete = (0, import_react3.useCallback)(() => {
586
+ const handleConfirmDelete = (0, import_react4.useCallback)(() => {
533
587
  if (confirmDeleteId) {
534
588
  onDelete(confirmDeleteId);
535
589
  if (editingId === confirmDeleteId) setEditingId(null);
536
590
  }
537
591
  setConfirmDeleteId(null);
538
592
  }, [confirmDeleteId, editingId, onDelete]);
539
- const sortedProfiles = (0, import_react3.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
593
+ const sortedProfiles = (0, import_react4.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
540
594
  const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
541
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
542
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
595
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
543
597
  headerLeft,
544
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
598
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles4.title, { color: fg }], children: "Profiles" })
545
599
  ] }),
546
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.KeyboardAvoidingView, { behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
600
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native4.KeyboardAvoidingView, { behavior: import_react_native4.Platform.OS === "ios" ? "padding" : void 0, style: styles4.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper4.FadingScrollView, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
547
601
  if (row.kind === "new") {
548
602
  if (editingId !== "new") {
549
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
550
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.View, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Icon, { source: "plus", size: 20, color: fg }) }),
551
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
603
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit("new"), style: styles4.row, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.rowInner, children: [
604
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native4.View, { style: [styles4.rowChip, styles4.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Icon, { source: "plus", size: 20, color: fg }) }),
605
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
552
606
  ] }) }, "new");
553
607
  }
554
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
608
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
555
609
  }
556
610
  const { profile } = row;
557
611
  if (editingId === profile.id) {
558
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
612
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
559
613
  }
560
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
561
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
562
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
614
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit(profile), style: styles4.row, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.rowInner, children: [
615
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: [styles4.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
563
617
  ] }) }, profile.id);
564
618
  }) }) }),
565
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.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) })
619
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_hud2.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) })
566
620
  ] });
567
621
  }
568
622
  function AlignedColorPicker({ useAlign, ...props }) {
569
- const { width: windowWidth } = (0, import_react_native3.useWindowDimensions)();
570
- const { width, height } = (0, import_hud2.getInlineColorPickerContentSize)(import_auto_paper3.defaultColors.length, windowWidth);
623
+ const { width: windowWidth } = (0, import_react_native4.useWindowDimensions)();
624
+ const { width, height } = (0, import_hud2.getInlineColorPickerContentSize)(import_auto_paper4.defaultColors.length, windowWidth);
571
625
  const alignOverride = useAlign(props.host.openId === props.id, width, height);
572
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
626
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
573
627
  }
574
628
  function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
575
629
  const register = (0, import_focus_chain.useFocusChain)();
576
630
  const tag = register();
577
631
  const name = register();
578
- const tagFocusRef = (0, import_react3.useRef)(tag.props.focus);
579
- (0, import_react3.useEffect)(() => {
632
+ const tagFocusRef = (0, import_react4.useRef)(tag.props.focus);
633
+ (0, import_react4.useEffect)(() => {
580
634
  tagFocusRef.current = tag.props.focus;
581
635
  });
582
- (0, import_react3.useEffect)(() => {
636
+ (0, import_react4.useEffect)(() => {
583
637
  if (host.openId === "color") tagFocusRef.current();
584
638
  }, [host.openId]);
585
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
586
- useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
587
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
588
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.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 }),
589
- onDelete && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press2.TouchableRipple, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
639
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.editRow, children: [
640
+ useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles4.hiddenTagInput, pointerEvents: "none" }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.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 }),
643
+ onDelete && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: onDelete, style: styles4.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
590
644
  ] });
591
645
  }
592
- var styles3 = import_react_native3.StyleSheet.create({
646
+ var styles4 = import_react_native4.StyleSheet.create({
593
647
  content: {
594
648
  gap: 4,
595
649
  paddingHorizontal: 20
@@ -668,23 +722,23 @@ var styles3 = import_react_native3.StyleSheet.create({
668
722
  });
669
723
 
670
724
  // src/ProfilesScreen.tsx
671
- var import_auto_paper4 = require("@rific/auto-paper");
672
- var import_feedback_press3 = require("@rific/feedback-press");
673
- var import_react4 = require("react");
674
- var import_react_native4 = require("react-native");
675
- var import_jsx_runtime6 = require("react/jsx-runtime");
725
+ var import_auto_paper5 = require("@rific/auto-paper");
726
+ var import_feedback_press4 = require("@rific/feedback-press");
727
+ var import_react5 = require("react");
728
+ var import_react_native5 = require("react-native");
729
+ var import_jsx_runtime8 = require("react/jsx-runtime");
676
730
  function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
677
- const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
678
- const managerRef = (0, import_react4.useRef)(null);
731
+ const { dark } = (0, import_auto_paper5.useAutoPaperTheme)();
732
+ const managerRef = (0, import_react5.useRef)(null);
679
733
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
680
734
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
681
735
  const handleBack = () => {
682
736
  managerRef.current?.commitPendingEdit();
683
737
  onBack();
684
738
  };
685
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native4.View, { style: [styles4.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
739
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.View, { style: [styles5.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press4.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
686
740
  }
687
- var styles4 = import_react_native4.StyleSheet.create({
741
+ var styles5 = import_react_native5.StyleSheet.create({
688
742
  container: {
689
743
  flex: 1
690
744
  }
@@ -848,36 +902,67 @@ async function resolveInitialProfiles(groupId, localFallback) {
848
902
 
849
903
  // src/redux/seatColorsSlice.ts
850
904
  var REHYDRATE2 = "persist/REHYDRATE";
905
+ function sameColor(a, b) {
906
+ return a.toLowerCase() === b.toLowerCase();
907
+ }
908
+ function profileSeatColor(profile, profileColors) {
909
+ const pick = profileColors[profile.id];
910
+ return pick && sameColor(pick.profileColor, profile.color) ? pick.color : profile.color;
911
+ }
851
912
  function createSeatColorsSlice(namespace, defaultState, mountKey = "seatColors") {
852
913
  const setLastCpuColor = createActionCreator(`seatColors/${namespace}/setLastCpuColor`);
853
914
  const setLastGuestColor = createActionCreator(`seatColors/${namespace}/setLastGuestColor`);
854
- const setProfileOverride = createActionCreator(`seatColors/${namespace}/setProfileOverride`);
915
+ const setProfileColor = createActionCreator(`seatColors/${namespace}/setProfileColor`);
916
+ function withoutPick(state, id) {
917
+ if (!state.profileColors[id]) return state;
918
+ const profileColors = { ...state.profileColors };
919
+ delete profileColors[id];
920
+ return { ...state, profileColors };
921
+ }
855
922
  function reducer(state = defaultState, action) {
856
923
  if (action.type === REHYDRATE2) {
857
924
  const persisted = action.payload?.[mountKey];
858
- return { ...defaultState, ...state, ...persisted };
925
+ const next = { ...defaultState, ...state, ...persisted };
926
+ delete next.profileOverride;
927
+ return next;
859
928
  }
860
929
  if (setLastCpuColor.match(action)) return { ...state, lastCpuColor: action.payload };
861
930
  if (setLastGuestColor.match(action)) return { ...state, lastGuestColor: { ...state.lastGuestColor, [action.payload.seat]: action.payload.color } };
862
- if (setProfileOverride.match(action)) return { ...state, profileOverride: { ...state.profileOverride, [action.payload.seat]: action.payload.color } };
931
+ if (setProfileColor.match(action)) {
932
+ const { profile, color } = action.payload;
933
+ if (color === null || sameColor(color, profile.color)) return withoutPick(state, profile.id);
934
+ return { ...state, profileColors: { ...state.profileColors, [profile.id]: { color, profileColor: profile.color } } };
935
+ }
936
+ if (profilesActions.remove.match(action)) return withoutPick(state, action.payload);
937
+ if (profilesActions.update.match(action)) {
938
+ const { id, patch } = action.payload;
939
+ const pick = state.profileColors[id];
940
+ return pick && patch.color !== void 0 && !sameColor(patch.color, pick.profileColor) ? withoutPick(state, id) : state;
941
+ }
942
+ if (profilesActions.setAll.match(action)) {
943
+ return action.payload.reduce((next, profile) => {
944
+ const pick = next.profileColors[profile.id];
945
+ return pick && !sameColor(profile.color, pick.profileColor) ? withoutPick(next, profile.id) : next;
946
+ }, state);
947
+ }
863
948
  return state;
864
949
  }
865
- return { actions: { setLastCpuColor, setLastGuestColor, setProfileOverride }, reducer };
950
+ return { actions: { setLastCpuColor, setLastGuestColor, setProfileColor }, reducer };
866
951
  }
867
952
 
868
953
  // src/redux/useSharedProfilesSync.ts
869
- var import_react5 = require("react");
870
- var import_react_native5 = require("react-native");
954
+ var import_react6 = require("react");
955
+ var import_react_native6 = require("react-native");
871
956
  function useSharedProfilesSync({ groupId, onRemoteChange }) {
872
- const pendingWriteRef = (0, import_react5.useRef)(false);
873
- const onRemoteChangeRef = (0, import_react5.useRef)(onRemoteChange);
874
- (0, import_react5.useEffect)(() => {
957
+ const pendingWriteRef = (0, import_react6.useRef)(false);
958
+ const onRemoteChangeRef = (0, import_react6.useRef)(onRemoteChange);
959
+ (0, import_react6.useEffect)(() => {
875
960
  onRemoteChangeRef.current = onRemoteChange;
876
961
  }, [onRemoteChange]);
877
- (0, import_react5.useEffect)(() => {
962
+ (0, import_react6.useEffect)(() => {
878
963
  if (!isSharedProfileStoreAvailable) return;
879
964
  let cancelled = false;
880
- const sub = import_react_native5.AppState.addEventListener("change", (next) => {
965
+ const sub = import_react_native6.AppState.addEventListener("change", (next) => {
881
966
  if (next !== "active" || pendingWriteRef.current) return;
882
967
  readSharedProfiles(groupId).then((shared) => {
883
968
  if (!cancelled && shared !== null) onRemoteChangeRef.current(shared);
@@ -888,7 +973,7 @@ function useSharedProfilesSync({ groupId, onRemoteChange }) {
888
973
  sub.remove();
889
974
  };
890
975
  }, [groupId]);
891
- const syncToShared = (0, import_react5.useCallback)(
976
+ const syncToShared = (0, import_react6.useCallback)(
892
977
  (next) => {
893
978
  if (!isSharedProfileStoreAvailable) return;
894
979
  pendingWriteRef.current = true;
@@ -912,6 +997,7 @@ function avoidSeatColorClash(p1Color, p2Color, defaults) {
912
997
  0 && (module.exports = {
913
998
  MAX_PROFILE_NAME_LENGTH,
914
999
  MAX_TAG_LENGTH,
1000
+ OptionPicker,
915
1001
  ProfileChip,
916
1002
  ProfileColorProvider,
917
1003
  ProfilePicker,
@@ -922,11 +1008,13 @@ function avoidSeatColorClash(p1Color, p2Color, defaults) {
922
1008
  createProfileRecord,
923
1009
  createProfileSelectionSlice,
924
1010
  createSeatColorsSlice,
1011
+ getOptionPickerContentSize,
925
1012
  getProfilePickerContentSize,
926
1013
  isSharedProfileStoreAvailable,
927
1014
  isValidProfile,
928
1015
  isValidTag,
929
1016
  loadSharedProfiles,
1017
+ profileSeatColor,
930
1018
  profilesActions,
931
1019
  profilesReducer,
932
1020
  readSharedProfiles,