@tastic/profile 0.14.0 → 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,54 +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
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
171
- /* @__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: [
172
- chipTrigger && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IdentityChip, { color, tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon, size: CHIP_SIZE }),
173
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
174
- /* @__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 })
175
141
  ] }) }),
176
- /* @__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.jsx)(
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)(
177
143
  import_react_native2.View,
178
144
  {
179
145
  style: [
@@ -185,76 +151,82 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
185
151
  maxHeight
186
152
  }
187
153
  ],
188
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_auto_paper2.FadingScrollView, { contentContainerStyle: styles2.menuContent, 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: [
189
- colorSection && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
190
- /* @__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: [
191
- /* @__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 }) }),
192
- /* @__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 })
193
- ] }) }),
194
- sortedProfiles.map((profile) => {
195
- const taken = profile.id === takenId;
196
- const selected = selectedId === profile.id;
197
- const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
198
- 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: [
199
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileChip, { profile, filled: !selected }),
200
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
201
- import_react_native_paper2.Text,
202
- {
203
- style: [
204
- styles2.rowLabel,
205
- {
206
- color: selected ? (0, import_auto_paper2.getContrastColor)(rowColor) : fg
207
- }
208
- ],
209
- numberOfLines: 1,
210
- children: profile.name
211
- }
212
- )
213
- ] }) }, profile.id);
214
- }),
215
- onManage && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
216
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
217
- /* @__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: [
218
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "account-cog-outline", size: 16, color }),
219
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.manageLabel, { color: fg }], children: "Manage" })
220
- ] }) })
154
+ children: [
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
221
162
  ] })
222
- ] }) }, colorSection && colorPage ? "color" : "list")
163
+ ]
223
164
  }
224
165
  ) })
225
166
  ] });
226
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
+ }
227
175
  function ColorRow({ section, fg, dividerColor, onPress }) {
228
176
  const { value, previewValue, label = "Color", caption } = section;
229
177
  const drawn = previewValue ? previewValue(value) : value;
230
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
231
- /* @__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: [
232
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
233
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.colorRowText, children: [
234
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
235
- 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
236
184
  ] }),
237
- /* @__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 })
238
186
  ] }) }),
239
- /* @__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 }] })
240
188
  ] });
241
189
  }
242
- function ColorPage({ section, profileColor, fg, onBack }) {
243
- 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;
244
192
  const displayFor = previewValue ?? ((hex) => hex);
245
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
246
- /* @__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: [
247
- /* @__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 }) }),
248
- /* @__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 })
249
197
  ] }) }),
250
- /* @__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) => {
251
199
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
252
- 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;
253
203
  const swatchDisplay = displayFor(swatch.value);
254
- 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);
255
207
  }) })
256
208
  ] });
257
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
+ });
258
230
  var styles2 = import_react_native2.StyleSheet.create({
259
231
  anchor: {
260
232
  position: "relative"
@@ -298,38 +270,31 @@ var styles2 = import_react_native2.StyleSheet.create({
298
270
  colorRowText: {
299
271
  flex: 1
300
272
  },
301
- // Separates Manage (a navigation action) from the selectable rows above it - different kind of
302
- // action, not just another row in the same list. Tinted with the seat's own color (passed in as
303
- // 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.
304
277
  divider: {
305
278
  height: 1,
306
279
  marginVertical: 4,
307
280
  opacity: 0.25
308
281
  },
309
- // Matches ProfileChip's own default size/shape exactly (see this row's own render-site comment)
310
- // - no backgroundColor, unlike ProfileChip's own default: this row has no identity color of its
311
- // own to fill it with, and the render site now colors the icon directly instead.
312
- 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: {
313
292
  alignItems: "center",
314
293
  borderRadius: 11,
315
294
  height: 22,
316
295
  justifyContent: "center",
317
296
  width: 22
318
297
  },
319
- manageLabel: {
320
- fontSize: 13,
321
- fontWeight: "700",
322
- letterSpacing: 0.5
323
- },
324
- // Centered rather than left-aligned like a normal row - see this row's own render-site comment
325
- // for why (a footer action, not another selectable identity).
326
- manageRowInner: {
327
- alignItems: "center",
328
- flexDirection: "row",
329
- gap: 6,
330
- height: "100%",
331
- justifyContent: "center"
332
- },
333
298
  menu: {
334
299
  borderRadius: 12,
335
300
  borderWidth: MENU_BORDER_WIDTH,
@@ -345,29 +310,13 @@ var styles2 = import_react_native2.StyleSheet.create({
345
310
  menuContent: {
346
311
  padding: LIST_PADDING
347
312
  },
348
- // The selected profile's own color (see ColorSection).
349
- profileDot: {
350
- borderRadius: 4,
351
- height: 8,
352
- width: 8
353
- },
354
- row: {
355
- borderRadius: 8,
356
- height: ROW_HEIGHT
357
- },
358
- rowDisabled: {
359
- opacity: 0.35
313
+ // With a footer pinned below, its own padding takes the bottom edge (same total height as before).
314
+ menuContentAboveFooter: {
315
+ paddingBottom: 0
360
316
  },
361
- rowInner: {
362
- alignItems: "center",
363
- flexDirection: "row",
364
- flex: 1,
365
- gap: 8,
366
- paddingHorizontal: ROW_INSET
367
- },
368
- rowLabel: {
369
- flex: 1,
370
- fontSize: 14
317
+ menuFooter: {
318
+ paddingBottom: LIST_PADDING,
319
+ paddingHorizontal: LIST_PADDING
371
320
  },
372
321
  swatch: {
373
322
  alignItems: "center",
@@ -380,6 +329,9 @@ var styles2 = import_react_native2.StyleSheet.create({
380
329
  borderColor: "#ffffff",
381
330
  borderWidth: 2
382
331
  },
332
+ swatchTaken: {
333
+ opacity: 0.35
334
+ },
383
335
  swatches: {
384
336
  columnGap: GRID_GAP,
385
337
  flexDirection: "row",
@@ -406,15 +358,128 @@ var styles2 = import_react_native2.StyleSheet.create({
406
358
  }
407
359
  });
408
360
 
409
- // 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
410
399
  var import_auto_paper3 = require("@rific/auto-paper");
411
400
  var import_feedback_press2 = require("@rific/feedback-press");
412
- var import_focus_chain = require("@rific/focus-chain");
413
- var import_toaster = require("@rific/toaster");
414
- var import_hud2 = require("@tastic/hud");
415
401
  var import_react3 = require("react");
416
402
  var import_react_native3 = require("react-native");
417
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");
418
483
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
419
484
 
420
485
  // src/profilesValidation.ts
@@ -450,7 +515,7 @@ function isValidProfile(value) {
450
515
  }
451
516
 
452
517
  // src/ProfilesManager.tsx
453
- var import_jsx_runtime5 = require("react/jsx-runtime");
518
+ var import_jsx_runtime7 = require("react/jsx-runtime");
454
519
  var CHIP_SIZE2 = 40;
455
520
  function deriveTag(name) {
456
521
  const trimmed = name.trim();
@@ -459,19 +524,19 @@ function deriveTag(name) {
459
524
  function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft, ref, fg: fgOverride, fgMuted: fgMutedOverride, titleVariant = "headlineSmall", colorPreview: colorPreviewProp, useColorPickerAlign }) {
460
525
  const contextColorPreview = useProfileColorPreview();
461
526
  const colorPreview = colorPreviewProp ?? contextColorPreview;
462
- const { dark } = (0, import_auto_paper3.useAutoPaperTheme)();
527
+ const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
463
528
  const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
464
529
  const { error: showErrorToast } = (0, import_toaster.useToast)();
465
530
  const host = (0, import_hud2.usePopoverHost)();
466
531
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
467
532
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
468
- const [editingId, setEditingId] = (0, import_react3.useState)(null);
469
- const [draftName, setDraftName] = (0, import_react3.useState)("");
470
- const [draftColor, setDraftColor] = (0, import_react3.useState)(defaultColor);
471
- const [draftTag, setDraftTag] = (0, import_react3.useState)("");
472
- const [tagManuallySet, setTagManuallySet] = (0, import_react3.useState)(false);
473
- const [confirmDeleteId, setConfirmDeleteId] = (0, import_react3.useState)(null);
474
- 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)(() => {
475
540
  const name = draftName.trim();
476
541
  if (editingId && name) {
477
542
  const tagValid = isValidTag(draftTag);
@@ -482,13 +547,13 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
482
547
  }
483
548
  setEditingId(null);
484
549
  }, [editingId, draftName, draftColor, draftTag, onCreate, onSave, showErrorToast]);
485
- const commitEditRef = (0, import_react3.useRef)(commitEdit);
486
- (0, import_react3.useEffect)(() => {
550
+ const commitEditRef = (0, import_react4.useRef)(commitEdit);
551
+ (0, import_react4.useEffect)(() => {
487
552
  commitEditRef.current = commitEdit;
488
553
  });
489
- (0, import_react3.useEffect)(() => () => commitEditRef.current(), []);
490
- (0, import_react3.useImperativeHandle)(ref, () => ({ commitPendingEdit: () => commitEditRef.current() }), []);
491
- 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)(
492
557
  (target) => {
493
558
  commitEdit();
494
559
  if (target === "new") {
@@ -506,79 +571,79 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
506
571
  },
507
572
  [commitEdit, defaultColor]
508
573
  );
509
- const handleNameChange = (0, import_react3.useCallback)(
574
+ const handleNameChange = (0, import_react4.useCallback)(
510
575
  (name) => {
511
576
  setDraftName(name);
512
577
  if (!tagManuallySet) setDraftTag(deriveTag(name));
513
578
  },
514
579
  [tagManuallySet]
515
580
  );
516
- const handleTagChange = (0, import_react3.useCallback)((tag) => {
581
+ const handleTagChange = (0, import_react4.useCallback)((tag) => {
517
582
  setDraftTag((previous) => clampTag(previous, tag));
518
583
  setTagManuallySet(true);
519
584
  }, []);
520
585
  const confirmDeleteProfile = profiles.find((p) => p.id === confirmDeleteId) ?? null;
521
- const handleConfirmDelete = (0, import_react3.useCallback)(() => {
586
+ const handleConfirmDelete = (0, import_react4.useCallback)(() => {
522
587
  if (confirmDeleteId) {
523
588
  onDelete(confirmDeleteId);
524
589
  if (editingId === confirmDeleteId) setEditingId(null);
525
590
  }
526
591
  setConfirmDeleteId(null);
527
592
  }, [confirmDeleteId, editingId, onDelete]);
528
- 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]);
529
594
  const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
530
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
531
- /* @__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: [
532
597
  headerLeft,
533
- /* @__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" })
534
599
  ] }),
535
- /* @__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) => {
536
601
  if (row.kind === "new") {
537
602
  if (editingId !== "new") {
538
- 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: [
539
- /* @__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 }) }),
540
- /* @__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" })
541
606
  ] }) }, "new");
542
607
  }
543
- 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");
544
609
  }
545
610
  const { profile } = row;
546
611
  if (editingId === profile.id) {
547
- 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);
548
613
  }
549
- 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: [
550
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
551
- /* @__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 })
552
617
  ] }) }, profile.id);
553
618
  }) }) }),
554
- /* @__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) })
555
620
  ] });
556
621
  }
557
622
  function AlignedColorPicker({ useAlign, ...props }) {
558
- const { width: windowWidth } = (0, import_react_native3.useWindowDimensions)();
559
- 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);
560
625
  const alignOverride = useAlign(props.host.openId === props.id, width, height);
561
- 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 });
562
627
  }
563
628
  function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
564
629
  const register = (0, import_focus_chain.useFocusChain)();
565
630
  const tag = register();
566
631
  const name = register();
567
- const tagFocusRef = (0, import_react3.useRef)(tag.props.focus);
568
- (0, import_react3.useEffect)(() => {
632
+ const tagFocusRef = (0, import_react4.useRef)(tag.props.focus);
633
+ (0, import_react4.useEffect)(() => {
569
634
  tagFocusRef.current = tag.props.focus;
570
635
  });
571
- (0, import_react3.useEffect)(() => {
636
+ (0, import_react4.useEffect)(() => {
572
637
  if (host.openId === "color") tagFocusRef.current();
573
638
  }, [host.openId]);
574
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
575
- 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 }),
576
- /* @__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" }),
577
- /* @__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 }),
578
- 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 }) })
579
644
  ] });
580
645
  }
581
- var styles3 = import_react_native3.StyleSheet.create({
646
+ var styles4 = import_react_native4.StyleSheet.create({
582
647
  content: {
583
648
  gap: 4,
584
649
  paddingHorizontal: 20
@@ -657,23 +722,23 @@ var styles3 = import_react_native3.StyleSheet.create({
657
722
  });
658
723
 
659
724
  // src/ProfilesScreen.tsx
660
- var import_auto_paper4 = require("@rific/auto-paper");
661
- var import_feedback_press3 = require("@rific/feedback-press");
662
- var import_react4 = require("react");
663
- var import_react_native4 = require("react-native");
664
- 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");
665
730
  function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
666
- const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
667
- const managerRef = (0, import_react4.useRef)(null);
731
+ const { dark } = (0, import_auto_paper5.useAutoPaperTheme)();
732
+ const managerRef = (0, import_react5.useRef)(null);
668
733
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
669
734
  const bg = bgOverride ?? (dark ? "#000000" : "#FFFFFF");
670
735
  const handleBack = () => {
671
736
  managerRef.current?.commitPendingEdit();
672
737
  onBack();
673
738
  };
674
- 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" }) }) });
675
740
  }
676
- var styles4 = import_react_native4.StyleSheet.create({
741
+ var styles5 = import_react_native5.StyleSheet.create({
677
742
  container: {
678
743
  flex: 1
679
744
  }
@@ -837,36 +902,67 @@ async function resolveInitialProfiles(groupId, localFallback) {
837
902
 
838
903
  // src/redux/seatColorsSlice.ts
839
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
+ }
840
912
  function createSeatColorsSlice(namespace, defaultState, mountKey = "seatColors") {
841
913
  const setLastCpuColor = createActionCreator(`seatColors/${namespace}/setLastCpuColor`);
842
914
  const setLastGuestColor = createActionCreator(`seatColors/${namespace}/setLastGuestColor`);
843
- 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
+ }
844
922
  function reducer(state = defaultState, action) {
845
923
  if (action.type === REHYDRATE2) {
846
924
  const persisted = action.payload?.[mountKey];
847
- return { ...defaultState, ...state, ...persisted };
925
+ const next = { ...defaultState, ...state, ...persisted };
926
+ delete next.profileOverride;
927
+ return next;
848
928
  }
849
929
  if (setLastCpuColor.match(action)) return { ...state, lastCpuColor: action.payload };
850
930
  if (setLastGuestColor.match(action)) return { ...state, lastGuestColor: { ...state.lastGuestColor, [action.payload.seat]: action.payload.color } };
851
- 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
+ }
852
948
  return state;
853
949
  }
854
- return { actions: { setLastCpuColor, setLastGuestColor, setProfileOverride }, reducer };
950
+ return { actions: { setLastCpuColor, setLastGuestColor, setProfileColor }, reducer };
855
951
  }
856
952
 
857
953
  // src/redux/useSharedProfilesSync.ts
858
- var import_react5 = require("react");
859
- var import_react_native5 = require("react-native");
954
+ var import_react6 = require("react");
955
+ var import_react_native6 = require("react-native");
860
956
  function useSharedProfilesSync({ groupId, onRemoteChange }) {
861
- const pendingWriteRef = (0, import_react5.useRef)(false);
862
- const onRemoteChangeRef = (0, import_react5.useRef)(onRemoteChange);
863
- (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)(() => {
864
960
  onRemoteChangeRef.current = onRemoteChange;
865
961
  }, [onRemoteChange]);
866
- (0, import_react5.useEffect)(() => {
962
+ (0, import_react6.useEffect)(() => {
867
963
  if (!isSharedProfileStoreAvailable) return;
868
964
  let cancelled = false;
869
- const sub = import_react_native5.AppState.addEventListener("change", (next) => {
965
+ const sub = import_react_native6.AppState.addEventListener("change", (next) => {
870
966
  if (next !== "active" || pendingWriteRef.current) return;
871
967
  readSharedProfiles(groupId).then((shared) => {
872
968
  if (!cancelled && shared !== null) onRemoteChangeRef.current(shared);
@@ -877,7 +973,7 @@ function useSharedProfilesSync({ groupId, onRemoteChange }) {
877
973
  sub.remove();
878
974
  };
879
975
  }, [groupId]);
880
- const syncToShared = (0, import_react5.useCallback)(
976
+ const syncToShared = (0, import_react6.useCallback)(
881
977
  (next) => {
882
978
  if (!isSharedProfileStoreAvailable) return;
883
979
  pendingWriteRef.current = true;
@@ -901,6 +997,7 @@ function avoidSeatColorClash(p1Color, p2Color, defaults) {
901
997
  0 && (module.exports = {
902
998
  MAX_PROFILE_NAME_LENGTH,
903
999
  MAX_TAG_LENGTH,
1000
+ OptionPicker,
904
1001
  ProfileChip,
905
1002
  ProfileColorProvider,
906
1003
  ProfilePicker,
@@ -911,11 +1008,13 @@ function avoidSeatColorClash(p1Color, p2Color, defaults) {
911
1008
  createProfileRecord,
912
1009
  createProfileSelectionSlice,
913
1010
  createSeatColorsSlice,
1011
+ getOptionPickerContentSize,
914
1012
  getProfilePickerContentSize,
915
1013
  isSharedProfileStoreAvailable,
916
1014
  isValidProfile,
917
1015
  isValidTag,
918
1016
  loadSharedProfiles,
1017
+ profileSeatColor,
919
1018
  profilesActions,
920
1019
  profilesReducer,
921
1020
  readSharedProfiles,