@tastic/profile 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -48,17 +48,34 @@ __export(index_exports, {
48
48
  });
49
49
  module.exports = __toCommonJS(index_exports);
50
50
 
51
- // src/ProfileChip.tsx
51
+ // src/IdentityChip.tsx
52
52
  var import_auto_paper = require("@rific/auto-paper");
53
53
  var import_react_native = require("react-native");
54
54
  var import_react_native_paper = require("react-native-paper");
55
+ var import_jsx_runtime = require("react/jsx-runtime");
56
+ var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
57
+ function IdentityChip({ color, tag, icon, size, filled = true }) {
58
+ const contrastColor = (0, import_auto_paper.getContrastColor)(color);
59
+ const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
60
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }], children: tag ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: icon, size: size * 0.6, color: contrastColor }) });
61
+ }
62
+ var styles = import_react_native.StyleSheet.create({
63
+ chip: {
64
+ alignItems: "center",
65
+ justifyContent: "center"
66
+ },
67
+ tag: {
68
+ fontWeight: "700",
69
+ paddingHorizontal: 2
70
+ }
71
+ });
55
72
 
56
73
  // src/ProfileColorContext.tsx
57
74
  var import_react = require("react");
58
- var import_jsx_runtime = require("react/jsx-runtime");
75
+ var import_jsx_runtime2 = require("react/jsx-runtime");
59
76
  var ProfileColorContext = (0, import_react.createContext)(void 0);
60
77
  function ProfileColorProvider({ preview, children }) {
61
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileColorContext.Provider, { value: preview, children });
78
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ProfileColorContext.Provider, { value: preview, children });
62
79
  }
63
80
  function useProfileColorPreview() {
64
81
  return (0, import_react.useContext)(ProfileColorContext);
@@ -69,24 +86,11 @@ function useProfileDisplayColor(hex) {
69
86
  }
70
87
 
71
88
  // src/ProfileChip.tsx
72
- var import_jsx_runtime2 = require("react/jsx-runtime");
73
- var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
89
+ var import_jsx_runtime3 = require("react/jsx-runtime");
74
90
  function ProfileChip({ profile, size = 22, filled = true }) {
75
91
  const color = useProfileDisplayColor(profile.color);
76
- const contrastColor = (0, import_auto_paper.getContrastColor)(color);
77
- const tagFontSize = size * (profile.tag && EMOJI_PATTERN.test(profile.tag) ? 0.6 : 0.4);
78
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }], children: profile.tag ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: profile.tag }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: "account", size: size * 0.6, color: contrastColor }) });
92
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
79
93
  }
80
- var styles = import_react_native.StyleSheet.create({
81
- chip: {
82
- alignItems: "center",
83
- justifyContent: "center"
84
- },
85
- tag: {
86
- fontWeight: "700",
87
- paddingHorizontal: 2
88
- }
89
- });
90
94
 
91
95
  // src/ProfilePicker.tsx
92
96
  var import_auto_paper2 = require("@rific/auto-paper");
@@ -95,16 +99,40 @@ var import_hud = require("@tastic/hud");
95
99
  var import_react2 = require("react");
96
100
  var import_react_native2 = require("react-native");
97
101
  var import_react_native_paper2 = require("react-native-paper");
98
- var import_jsx_runtime3 = require("react/jsx-runtime");
102
+ var import_jsx_runtime4 = require("react/jsx-runtime");
99
103
  var TRIGGER_HEIGHT = 28;
100
104
  var POPOVER_WIDTH = 220;
101
105
  var ROW_HEIGHT = 36;
102
106
  var LIST_PADDING = 8;
103
- function getProfilePickerContentSize(profileCount, hasManageRow = false) {
107
+ var MENU_BORDER_WIDTH = 2;
108
+ var ROW_INSET = 12;
109
+ var CHIP_SIZE = 32;
110
+ var CHIP_TRIGGER_PADDING = 6;
111
+ var SWATCH_SIZE = 28;
112
+ var SWATCH_MIN_GAP = 6;
113
+ var COLOR_ROW_CAPTIONED_HEIGHT = 48;
114
+ var COLOR_LABEL_LINE = 18;
115
+ var COLOR_CAPTION_LINE = 16;
116
+ var COLOR_ROW_DIVIDER = 9;
117
+ var GRID_TOP = 4;
118
+ var GRID_BOTTOM = 8;
119
+ var GRID_WIDTH = POPOVER_WIDTH - 2 * MENU_BORDER_WIDTH - 2 * LIST_PADDING - 2 * ROW_INSET;
120
+ var GRID_COLUMNS = Math.floor((GRID_WIDTH + SWATCH_MIN_GAP) / (SWATCH_SIZE + SWATCH_MIN_GAP));
121
+ var GRID_GAP = (GRID_WIDTH - GRID_COLUMNS * SWATCH_SIZE) / (GRID_COLUMNS - 1);
122
+ function colorRowHeight(caption) {
123
+ return caption ? COLOR_ROW_CAPTIONED_HEIGHT : ROW_HEIGHT;
124
+ }
125
+ function colorPageHeight(swatches) {
126
+ const rows = Math.ceil(swatches.length / GRID_COLUMNS);
127
+ return ROW_HEIGHT + GRID_TOP + rows * SWATCH_SIZE + (rows - 1) * GRID_GAP + GRID_BOTTOM;
128
+ }
129
+ function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
104
130
  const rowCount = profileCount + 1 + (hasManageRow ? 1 : 0);
105
- return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + rowCount * ROW_HEIGHT };
131
+ const list = rowCount * ROW_HEIGHT + (colorSection ? colorRowHeight(colorSection.caption) + COLOR_ROW_DIVIDER : 0);
132
+ const content = colorSection ? Math.max(list, colorPageHeight(colorSection.swatches ?? import_auto_paper2.defaultColors)) : list;
133
+ return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + content };
106
134
  }
107
- function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage }) {
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 }) {
108
136
  const id = `${idPrefix}-profile`;
109
137
  const colorPreview = useProfileColorPreview();
110
138
  const menuBg = dark ? "#000000" : "#FFFFFF";
@@ -112,7 +140,7 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
112
140
  const open = host.openId === id;
113
141
  const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
114
142
  const sortedProfiles = (0, import_react2.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
115
- const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage);
143
+ const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
116
144
  const auto = (0, import_hud.useAutoAlign)(open, contentWidth, contentHeight, rotation);
117
145
  const placement = alignOverride ?? auto;
118
146
  const { maxHeight, measured, triggerRef, verticalAlign } = placement;
@@ -130,13 +158,24 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
130
158
  host.close();
131
159
  onManage?.();
132
160
  };
133
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], children: [
134
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { onPress: () => host.toggle(id), style: styles2.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: styles2.triggerInner, children: [
135
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color }], numberOfLines: 1, children: (selectedProfile?.name ?? guestLabel).toUpperCase() }),
136
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color })
161
+ const chipTrigger = !!colorSection;
162
+ const [triggerWidth, setTriggerWidth] = (0, import_react2.useState)(0);
163
+ const handleAnchorLayout = chipTrigger ? (e) => setTriggerWidth(e.nativeEvent.layout.width) : void 0;
164
+ const [colorPage, setColorPage] = (0, import_react2.useState)(false);
165
+ const handleTriggerPress = () => {
166
+ setColorPage(false);
167
+ host.toggle(id);
168
+ };
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 })
137
176
  ] }) }),
138
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_hud.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
139
- import_react_native2.ScrollView,
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)(
178
+ import_react_native2.View,
140
179
  {
141
180
  style: [
142
181
  styles2.menu,
@@ -147,39 +186,40 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
147
186
  maxHeight
148
187
  }
149
188
  ],
150
- contentContainerStyle: styles2.menuContent,
151
- showsVerticalScrollIndicator: false,
152
189
  children: [
153
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: nullIcon, size: 13, color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }) }),
155
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles2.rowLabel, { color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }], children: nullLabel })
156
- ] }) }),
157
- sortedProfiles.map((profile) => {
158
- const taken = profile.id === takenId;
159
- const selected = selectedId === profile.id;
160
- const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
161
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
162
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProfileChip, { profile, filled: !selected }),
163
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
164
- import_react_native_paper2.Text,
165
- {
166
- style: [
167
- styles2.rowLabel,
168
- {
169
- color: selected ? (0, import_auto_paper2.getContrastColor)(rowColor) : fg
170
- }
171
- ],
172
- numberOfLines: 1,
173
- children: profile.name
174
- }
175
- )
176
- ] }) }, profile.id);
177
- }),
178
- onManage && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
179
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
180
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: styles2.manageRowInner, children: [
181
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "account-cog-outline", size: 16, color }),
182
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles2.manageLabel, { color }], children: "Manage" })
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" })
183
223
  ] }) })
184
224
  ] })
185
225
  ]
@@ -187,6 +227,37 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
187
227
  ) })
188
228
  ] });
189
229
  }
230
+ function ColorRow({ section, fg, dividerColor, onPress }) {
231
+ const { value, previewValue, label = "Color", caption } = section;
232
+ 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
239
+ ] }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-right", size: 18, color: fg })
241
+ ] }) }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: dividerColor }] })
243
+ ] });
244
+ }
245
+ function ColorPage({ section, profileColor, fg, onBack }) {
246
+ const { value, onChange, previewValue, swatches = import_auto_paper2.defaultColors, label = "Color" } = section;
247
+ 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 })
252
+ ] }) }),
253
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.swatches, children: swatches.map((swatch) => {
254
+ const selected = swatch.value.toLowerCase() === value.toLowerCase();
255
+ const isProfileColor = !!profileColor && swatch.value.toLowerCase() === profileColor.toLowerCase();
256
+ 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);
258
+ }) })
259
+ ] });
260
+ }
190
261
  var styles2 = import_react_native2.StyleSheet.create({
191
262
  anchor: {
192
263
  position: "relative"
@@ -196,6 +267,40 @@ var styles2 = import_react_native2.StyleSheet.create({
196
267
  anchorOpen: {
197
268
  zIndex: 100
198
269
  },
270
+ // The pill, rounded at both ends. The host adds its own background through `triggerStyle`.
271
+ chipTrigger: {
272
+ borderRadius: (CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING) / 2
273
+ },
274
+ // The padding lives on the measured view, not the pressable around it, so the placement measures
275
+ // the pill's real edge: measured from inside the padding, a popover opening toward a screen edge
276
+ // was sized as if the pill were 6pt further from it, and ran 2pt past that edge.
277
+ chipTriggerInner: {
278
+ alignItems: "center",
279
+ flexDirection: "row",
280
+ gap: 8,
281
+ height: CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING,
282
+ paddingLeft: CHIP_TRIGGER_PADDING,
283
+ paddingRight: CHIP_TRIGGER_PADDING + 8
284
+ },
285
+ colorRowCaption: {
286
+ fontSize: 12,
287
+ lineHeight: COLOR_CAPTION_LINE,
288
+ opacity: 0.6
289
+ },
290
+ // A row's label, with a line height so the captioned Color row's height is exact.
291
+ colorRowLabel: {
292
+ fontSize: 14,
293
+ lineHeight: COLOR_LABEL_LINE
294
+ },
295
+ // The color in effect, in the same 22pt slot as every row's chip.
296
+ colorRowSwatch: {
297
+ borderRadius: 11,
298
+ height: 22,
299
+ width: 22
300
+ },
301
+ colorRowText: {
302
+ flex: 1
303
+ },
199
304
  // Separates Manage (a navigation action) from the selectable rows above it - different kind of
200
305
  // action, not just another row in the same list. Tinted with the seat's own color (passed in as
201
306
  // backgroundColor) rather than a flat gray, matching the border/caret's own accent-tinted look.
@@ -230,7 +335,7 @@ var styles2 = import_react_native2.StyleSheet.create({
230
335
  },
231
336
  menu: {
232
337
  borderRadius: 12,
233
- borderWidth: 2,
338
+ borderWidth: MENU_BORDER_WIDTH,
234
339
  elevation: 8,
235
340
  shadowColor: "#000000",
236
341
  shadowOffset: { height: 2, width: 0 },
@@ -243,6 +348,20 @@ var styles2 = import_react_native2.StyleSheet.create({
243
348
  menuContent: {
244
349
  padding: LIST_PADDING
245
350
  },
351
+ // With Manage pinned below, its own padding takes the bottom edge (same total height as before).
352
+ menuContentAboveFooter: {
353
+ paddingBottom: 0
354
+ },
355
+ menuFooter: {
356
+ paddingBottom: LIST_PADDING,
357
+ paddingHorizontal: LIST_PADDING
358
+ },
359
+ // The selected profile's own color (see ColorSection).
360
+ profileDot: {
361
+ borderRadius: 4,
362
+ height: 8,
363
+ width: 8
364
+ },
246
365
  row: {
247
366
  borderRadius: 8,
248
367
  height: ROW_HEIGHT
@@ -255,12 +374,33 @@ var styles2 = import_react_native2.StyleSheet.create({
255
374
  flexDirection: "row",
256
375
  flex: 1,
257
376
  gap: 8,
258
- paddingHorizontal: 12
377
+ paddingHorizontal: ROW_INSET
259
378
  },
260
379
  rowLabel: {
261
380
  flex: 1,
262
381
  fontSize: 14
263
382
  },
383
+ swatch: {
384
+ alignItems: "center",
385
+ borderRadius: SWATCH_SIZE / 2,
386
+ height: SWATCH_SIZE,
387
+ justifyContent: "center",
388
+ width: SWATCH_SIZE
389
+ },
390
+ swatchSelected: {
391
+ borderColor: "#ffffff",
392
+ borderWidth: 2
393
+ },
394
+ swatches: {
395
+ columnGap: GRID_GAP,
396
+ flexDirection: "row",
397
+ flexWrap: "wrap",
398
+ marginBottom: GRID_BOTTOM,
399
+ marginHorizontal: ROW_INSET,
400
+ marginTop: GRID_TOP,
401
+ rowGap: GRID_GAP,
402
+ width: GRID_WIDTH
403
+ },
264
404
  trigger: {
265
405
  alignSelf: "flex-start"
266
406
  },
@@ -321,8 +461,8 @@ function isValidProfile(value) {
321
461
  }
322
462
 
323
463
  // src/ProfilesManager.tsx
324
- var import_jsx_runtime4 = require("react/jsx-runtime");
325
- var CHIP_SIZE = 40;
464
+ var import_jsx_runtime5 = require("react/jsx-runtime");
465
+ var CHIP_SIZE2 = 40;
326
466
  function deriveTag(name) {
327
467
  const trimmed = name.trim();
328
468
  return trimmed ? trimmed[0].toUpperCase() : "";
@@ -398,38 +538,38 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
398
538
  }, [confirmDeleteId, editingId, onDelete]);
399
539
  const sortedProfiles = (0, import_react3.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
400
540
  const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
401
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
402
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
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: [
403
543
  headerLeft,
404
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
544
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
405
545
  ] }),
406
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.KeyboardAvoidingView, { behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
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) => {
407
547
  if (row.kind === "new") {
408
548
  if (editingId !== "new") {
409
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
410
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Icon, { source: "plus", size: 20, color: fg }) }),
411
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
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" })
412
552
  ] }) }, "new");
413
553
  }
414
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
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");
415
555
  }
416
556
  const { profile } = row;
417
557
  if (editingId === profile.id) {
418
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, 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);
419
559
  }
420
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
421
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileChip, { profile, size: CHIP_SIZE }),
422
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
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 })
423
563
  ] }) }, profile.id);
424
564
  }) }) }),
425
- /* @__PURE__ */ (0, import_jsx_runtime4.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) })
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) })
426
566
  ] });
427
567
  }
428
568
  function AlignedColorPicker({ useAlign, ...props }) {
429
569
  const { width: windowWidth } = (0, import_react_native3.useWindowDimensions)();
430
570
  const { width, height } = (0, import_hud2.getInlineColorPickerContentSize)(import_auto_paper3.defaultColors.length, windowWidth);
431
571
  const alignOverride = useAlign(props.host.openId === props.id, width, height);
432
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
572
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
433
573
  }
434
574
  function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
435
575
  const register = (0, import_focus_chain.useFocusChain)();
@@ -442,11 +582,11 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
442
582
  (0, import_react3.useEffect)(() => {
443
583
  if (host.openId === "color") tagFocusRef.current();
444
584
  }, [host.openId]);
445
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
446
- useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
447
- /* @__PURE__ */ (0, import_jsx_runtime4.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" }),
448
- /* @__PURE__ */ (0, import_jsx_runtime4.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 }),
449
- onDelete && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
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 }) })
450
590
  ] });
451
591
  }
452
592
  var styles3 = import_react_native3.StyleSheet.create({
@@ -454,11 +594,17 @@ var styles3 = import_react_native3.StyleSheet.create({
454
594
  gap: 4,
455
595
  paddingHorizontal: 20
456
596
  },
597
+ // zIndex: the color popover lives inside this row, and a zIndex only orders siblings of one parent
598
+ // (React Native Web also gives every positioned view its own stacking context), so the popover's
599
+ // own zIndex never reached the rows after this one: they were drawn over it (a Kid row and the New
600
+ // Profile row across the swatches, 2026-10-16). Raising the row lifts the popover with it. There
601
+ // is only ever one edit row.
457
602
  editRow: {
458
603
  alignItems: "center",
459
604
  flexDirection: "row",
460
605
  gap: 8,
461
- paddingVertical: 6
606
+ paddingVertical: 6,
607
+ zIndex: 1
462
608
  },
463
609
  header: {
464
610
  alignItems: "center",
@@ -499,10 +645,10 @@ var styles3 = import_react_native3.StyleSheet.create({
499
645
  },
500
646
  rowChip: {
501
647
  alignItems: "center",
502
- borderRadius: CHIP_SIZE / 2,
503
- height: CHIP_SIZE,
648
+ borderRadius: CHIP_SIZE2 / 2,
649
+ height: CHIP_SIZE2,
504
650
  justifyContent: "center",
505
- width: CHIP_SIZE
651
+ width: CHIP_SIZE2
506
652
  },
507
653
  rowInner: {
508
654
  alignItems: "center",
@@ -526,7 +672,7 @@ var import_auto_paper4 = require("@rific/auto-paper");
526
672
  var import_feedback_press3 = require("@rific/feedback-press");
527
673
  var import_react4 = require("react");
528
674
  var import_react_native4 = require("react-native");
529
- var import_jsx_runtime5 = require("react/jsx-runtime");
675
+ var import_jsx_runtime6 = require("react/jsx-runtime");
530
676
  function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
531
677
  const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
532
678
  const managerRef = (0, import_react4.useRef)(null);
@@ -536,7 +682,7 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
536
682
  managerRef.current?.commitPendingEdit();
537
683
  onBack();
538
684
  };
539
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.View, { style: [styles4.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
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" }) }) });
540
686
  }
541
687
  var styles4 = import_react_native4.StyleSheet.create({
542
688
  container: {